Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.regex.Pattern; /** * A peephole optimization that minimizes code by simplifying conditional * expressions, replacing IFs with HOOKs, replacing object constructors * with literals, and simplifying returns. * */ public class PeepholeSubstituteAlternateSyntax extends AbstractPeepholeOptimization { private static final int AND_PRECEDENCE = NodeUtil.precedence(Token.AND); private static final int OR_PRECEDENCE = NodeUtil.precedence(Token.OR); static final DiagnosticType INVALID_REGULAR_EXPRESSION_FLAGS = DiagnosticType.error( "JSC_INVALID_REGULAR_EXPRESSION_FLAGS", "Invalid flags to RegExp constructor: {0}"); static final Predicate<Node> DONT_TRAVERSE_FUNCTIONS_PREDICATE = new Predicate<Node>() { @Override public boolean apply(Node input) { return input.getType() != Token.FUNCTION; } }; /** * Tries apply our various peephole minimizations on the passed in node. */ @Override @SuppressWarnings("fallthrough") public Node optimizeSubtree(Node node) { switch(node.getType()) { case Token.RETURN: return tryReduceReturn(node); case Token.NOT: tryMinimizeCondition(node.getFirstChild()); return tryMinimizeNot(node); case Token.IF: tryMinimizeCondition(node.getFirstChild()); return tryMinimizeIf(node); case Token.EXPR_RESULT: tryMinimizeCondition(node.getFirstChild()); return node; case Token.HOOK: tryMinimizeCondition(node.getFirstChild()); return node; case Token.WHILE: case Token.DO: tryMinimizeCondition(NodeUtil.getConditionExpression(node)); return node; case Token.FOR: if (!NodeUtil.isForIn(node)) { tryMinimizeCondition(

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>NodeUtil.getConditionExpression(node)); } return node; case Token.NEW: node = tryFoldStandardConstructors(node); if (node.getType() != Token.CALL) { return node; } // Fall through on purpose because tryFoldStandardConstructors() may // convert a NEW node into a CALL node case Token.CALL: return tryFoldLiteralConstructor(node); default: return node; //Nothing changed } } /** * Reduce "return undefined" or "return void 0" to simply "return". * * Returns the replacement for n, or the original if no change was made. */ private Node tryReduceReturn(Node n) { Node result = n.getFirstChild(); boolean possibleException = result != null && ControlFlowAnalysis.mayThrowException(result); // Try to use a substitute that with a break because it is shorter. // First lets pretend it is a break with no labels. Node breakTarget = n; boolean safe = true; for (;!ControlFlowAnalysis.isBreakTarget(breakTarget, null /* no label */); breakTarget = breakTarget.getParent()) { if (NodeUtil.isFunction(breakTarget) || breakTarget.getType() == Token.SCRIPT) { // We can switch the return to a break if the return value has // side effect and it must encounter a finally. // example: return alert('a') -> finally { alert('b') } -> // return alert('a') // prints a then b. If the first return is a break, // it prints b then a. safe = false; break; } } Node follow = ControlFlowAnalysis.computeFollowNode(breakTarget); // Skip pass all the finally blocks because both the break and return will // also trigger all the finally blocks. However, the order of execution is // slightly changed. Consider: // // return a() -> finally { b() } -> return a() // // which would call a() first. However, changing the first return to a // break will result in calling b(). while (follow != null && NodeUtil.isTryFinallyNode(follow.getParent(), follow)) { if (result != null && // TODO(user): Use the new side effects API for more accuracy. (NodeUtil.canBeSideEffected(result) || NodeUtil.mayHaveSideEffects(result))) { safe = false; break; } follow = ControlFlowAnalysis.computeFollowNode(follow); } if (safe) { if (follow == null) { // When follow is null, this mean the follow of a break target is the // end of a function. This means a break is same as return. if (result == null) { n.setType(Token.BREAK); reportCodeChange(); return n; } } else if (follow.getType() == Token.RETURN && (result == follow.getFirstChild() || (result != null && follow.hasChildren() &&

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> result.checkTreeEqualsSilent(follow.getFirstChild())) && ControlFlowAnalysis.getExceptionHandler(n) == ControlFlowAnalysis.getExceptionHandler(follow) )) { // When the follow is a return, if both doesn't return anything // or both returns the same thing. This mean we can replace it with a // break. n.removeChildren(); n.setType(Token.BREAK); reportCodeChange(); return n; } // If any of the above is executed, we must return because n is no longer // a "return" node. } // TODO(user): consider cases such as if (x) { return 1} return 1; if (result != null) { switch (result.getType()) { case Token.VOID: Node operand = result.getFirstChild(); if (!mayHaveSideEffects(operand)) { n.removeFirstChild(); reportCodeChange(); } break; case Token.NAME: String name = result.getString(); if (name.equals("undefined")) { n.removeFirstChild(); reportCodeChange(); } break; default: //Do nothing break; } } return n; } /** * Try to minimize NOT nodes such as !(x==y). * * Returns the replacement for n or the original if no change was made */ private Node tryMinimizeNot(Node n) { Node parent = n.getParent(); Node notChild = n.getFirstChild(); // negative operator of the current one : == -> != for instance. int complementOperator; switch (notChild.getType()) { case Token.EQ: complementOperator = Token.NE; break; case Token.NE: complementOperator = Token.EQ; break; case Token.SHEQ: complementOperator = Token.SHNE; break; case Token.SHNE: complementOperator = Token.SHEQ; break; // GT, GE, LT, LE are not handled in this because !(x<NaN) != x>=NaN. default: return n; } Node newOperator = n.removeFirstChild(); newOperator.setType(complementOperator); parent.replaceChild(n, newOperator); reportCodeChange(); return newOperator; } /** * Try turning IF nodes into smaller HOOKs * * Returns the replacement for n or the original if no replacement was * necessary. */ private Node tryMinimizeIf(Node n) { Node parent = n.getParent(); Node cond = n.getFirstChild(); /* If the condition is a literal, we'll let other * optimizations try to remove useless code. */ if (NodeUtil.isLiteralValue(cond, true)) { return n; } Node thenBranch = cond.getNext(); Node elseBranch = thenBranch.getNext(); if (elseBranch == null) { if (isFoldableExpressBlock(thenBranch)) { Node expr = getBlockExpression(thenBranch);

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>, returnNode); reportCodeChange(); return returnNode; } boolean thenBranchIsExpressionBlock = isFoldableExpressBlock(thenBranch); boolean elseBranchIsExpressionBlock = isFoldableExpressBlock(elseBranch); if (thenBranchIsExpressionBlock && elseBranchIsExpressionBlock) { Node thenOp = getBlockExpression(thenBranch).getFirstChild(); Node elseOp = getBlockExpression(elseBranch).getFirstChild(); if (thenOp.getType() == elseOp.getType()) { // if(x)a=1;else a=2; -> a=x?1:2; if (NodeUtil.isAssignmentOp(thenOp)) { Node lhs = thenOp.getFirstChild(); if (areNodesEqualForInlining(lhs, elseOp.getFirstChild()) && // if LHS has side effects, don't proceed [since the optimization // evaluates LHS before cond] // NOTE - there are some circumstances where we can // proceed even if there are side effects... !mayEffectMutableState(lhs)) { n.removeChild(cond); Node assignName = thenOp.removeFirstChild(); Node thenExpr = thenOp.removeFirstChild(); Node elseExpr = elseOp.getLastChild(); elseOp.removeChild(elseExpr); Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr) .copyInformationFrom(n); Node assign = new Node(thenOp.getType(), assignName, hookNode) .copyInformationFrom(thenOp); Node expr = NodeUtil.newExpr(assign); parent.replaceChild(n, expr); reportCodeChange(); return expr; } } else if (NodeUtil.isCall(thenOp)) { // if(x)foo();else bar(); -> x?foo():bar() n.removeChild(cond); thenOp.detachFromParent(); elseOp.detachFromParent(); Node hookNode = new Node(Token.HOOK, cond, thenOp, elseOp) .copyInformationFrom(n); Node expr = NodeUtil.newExpr(hookNode); parent.replaceChild(n, expr); reportCodeChange(); return expr; } } return n; } boolean thenBranchIsVar = isVarBlock(thenBranch); boolean elseBranchIsVar = isVarBlock(elseBranch); // if(x)var y=1;else y=2 -> var y=x?1:2 if (thenBranchIsVar && elseBranchIsExpressionBlock && NodeUtil.isAssign(getBlockExpression(elseBranch).getFirstChild())) { Node var = getBlockVar(thenBranch); Node elseAssign = getBlockExpression(elseBranch).getFirstChild(); Node name1 = var.getFirstChild(); Node maybeName2 = elseAssign.getFirstChild(); if (name1.hasChildren() && maybeName2.getType() == Token.NAME && name1.getString().equals(maybeName2.getString())) { Node thenExpr = name1.removeChildren

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>(); Node elseExpr = elseAssign.getLastChild().detachFromParent(); cond.detachFromParent(); Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr) .copyInformationFrom(n); var.detachFromParent(); name1.addChildrenToBack(hookNode); parent.replaceChild(n, var); reportCodeChange(); return var; } // if(x)y=1;else var y=2 -> var y=x?1:2 } else if (elseBranchIsVar && thenBranchIsExpressionBlock && NodeUtil.isAssign(getBlockExpression(thenBranch).getFirstChild())) { Node var = getBlockVar(elseBranch); Node thenAssign = getBlockExpression(thenBranch).getFirstChild(); Node maybeName1 = thenAssign.getFirstChild(); Node name2 = var.getFirstChild(); if (name2.hasChildren() && maybeName1.getType() == Token.NAME && maybeName1.getString().equals(name2.getString())) { Node thenExpr = thenAssign.getLastChild().detachFromParent(); Node elseExpr = name2.removeChildren(); cond.detachFromParent(); Node hookNode = new Node(Token.HOOK, cond, thenExpr, elseExpr) .copyInformationFrom(n); var.detachFromParent(); name2.addChildrenToBack(hookNode); parent.replaceChild(n, var); reportCodeChange(); return var; } } return n; } /** * Try to remove duplicate statements from IF blocks. For example: * * if (a) { * x = 1; * return true; * } else { * x = 2; * return true; * } * * becomes: * * if (a) { * x = 1; * } else { * x = 2; * } * return true; * * @param n The IF node to examine. */ private void tryRemoveRepeatedStatements(Node n) { Preconditions.checkState(n.getType() == Token.IF); Node parent = n.getParent(); if (!NodeUtil.isStatementBlock(parent)) { // If the immediate parent is something like a label, we // can't move the statement, so bail. return; } Node cond = n.getFirstChild(); Node trueBranch = cond.getNext(); Node falseBranch = trueBranch.getNext(); Preconditions.checkNotNull(trueBranch); Preconditions.checkNotNull(falseBranch); while (true) { Node lastTrue = trueBranch.getLastChild(); Node lastFalse = falseBranch.getLastChild(); * @return Whether the node is a block with a single statement that is * an return. */ private boolean isReturnExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node first = n.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>getFirstChild(); if (first.getType() == Token.RETURN) { return first.hasOneChild(); } } } return false; } /** * @return The expression that is part of the return. */ private Node getBlockReturnExpression(Node n) { Preconditions.checkState(isReturnExpressBlock(n)); return n.getFirstChild().getFirstChild(); } /** * @return Whether the node is a block with a single statement that is * a VAR declaration of a single variable. */ private boolean isVarBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node first = n.getFirstChild(); if (first.getType() == Token.VAR) { return first.hasOneChild(); } } } return false; } /** * @return The var node. */ private Node getBlockVar(Node n) { Preconditions.checkState(isVarBlock(n)); return n.getFirstChild(); } /** * Does a statement consume a 'dangling else'? A statement consumes * a 'dangling else' if an 'else' token following the statement * would be considered by the parser to be part of the statement. */ private boolean consumesDanglingElse(Node n) { while (true) { switch (n.getType()) { case Token.IF: if (n.getChildCount() < 3) { return true; } // This IF node has no else clause. n = n.getLastChild(); continue; case Token.WITH: case Token.WHILE: case Token.FOR: n = n.getLastChild(); continue; default: return false; } } } /** * Does the expression contain an operator with lower precedence than * the argument? */ private boolean isLowerPrecedenceInExpression(Node n, final int precedence) { Predicate<Node> isLowerPrecedencePredicate = new Predicate<Node>() { @Override public boolean apply(Node input) { return NodeUtil.precedence(input.getType()) < precedence; } }; return NodeUtil.has(n, isLowerPrecedencePredicate, DONT_TRAVERSE_FUNCTIONS_PREDICATE); } /** * Does the expression contain a property assignment? */ private boolean isPropertyAssignmentInExpression(Node n) { Predicate<Node> isPropertyAssignmentInExpressionPredicate = new Predicate<Node>() { @Override public boolean apply(Node input) { return (input.getType() == Token.GETPROP && input.getParent().getType() == Token.ASSIGN); } }; return NodeUtil.has(n, isPropertyAssignmentInExpressionPredicate, DONT_TRAVERSE_FUNCTIONS_PREDICATE); } /** * Try to minimize conditions expressions, as there are additional * assumptions that can be made when it is known that the final result * is

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> a boolean. * * The following transformations are done recursively: * !(x||y) --> !x&&!y * !(x&&y) --> !x||!y * !!x --> x * Thus: * !(x&&!y) --> !x||!!y --> !x||y * * Returns the replacement for n, or the original if no change was made */ private Node tryMinimizeCondition(Node n) { Node parent = n.getParent(); switch (n.getType()) { case Token.NOT: Node first = n.getFirstChild(); switch (first.getType()) { case Token.NOT: { Node newRoot = first.removeFirstChild(); parent.replaceChild(n, newRoot); reportCodeChange(); // No need to traverse, tryMinimizeCondition is called on the // NOT children are handled below. return newRoot; } case Token.AND: case Token.OR: { Node leftParent = first.getFirstChild(); Node rightParent = first.getLastChild(); if (leftParent.getType() == Token.NOT && rightParent.getType() == Token.NOT) { Node left = leftParent.removeFirstChild(); Node right = rightParent.removeFirstChild(); int newOp = (first.getType() == Token.AND) ? Token.OR : Token.AND; Node newRoot = new Node(newOp, left, right); parent.replaceChild(n, newRoot); reportCodeChange(); // No need to traverse, tryMinimizeCondition is called on the // AND and OR children below. return newRoot; } } break; } // No need to traverse, tryMinimizeCondition is called on the NOT // children in the general case in the main post-order traversal. return n; case Token.OR: case Token.AND: { Node left = n.getFirstChild(); Node right = n.getLastChild(); // Because the expression is in a boolean context minimize // the children, this can't be done in the general case. left = tryMinimizeCondition(left); right = tryMinimizeCondition(right); // Remove useless conditionals // Handle four cases: // x || false --> x // x || true --> true // x && true --> x // x && false --> false TernaryValue rightVal = NodeUtil.getBooleanValue(right); if (NodeUtil.getBooleanValue(right) != TernaryValue.UNKNOWN) { int type = n.getType(); Node replacement = null; boolean rval = rightVal.toBoolean(true); // (x || FALSE) => x // (x && TRUE) => x if (type == Token.OR && !rval || type == Token.AND && rval) { replacement = left; } else if (!mayHaveSideEffects(left)) { replacement = right; } if (replacement != null) { n.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>detachChildren(); parent.replaceChild(n, replacement); reportCodeChange(); return replacement; } } return n; } case Token.HOOK: { Node condition = n.getFirstChild(); Node trueNode = n.getFirstChild().getNext(); Node falseNode = n.getLastChild(); // Because the expression is in a boolean context minimize // the result children, this can't be done in the general case. // The condition is handled in the general case in #optimizeSubtree trueNode = tryMinimizeCondition(trueNode); falseNode = tryMinimizeCondition(falseNode); // Handle four cases: // x ? true : false --> x // x ? false : true --> !x // x ? true : y --> x || y // x ? y : false --> x && y Node replacement = null; if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE && NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) { // Remove useless conditionals, keep the condition condition.detachFromParent(); replacement = condition; } else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.FALSE && NodeUtil.getBooleanValue(falseNode) == TernaryValue.TRUE) { // Remove useless conditionals, keep the condition condition.detachFromParent(); replacement = new Node(Token.NOT, condition); } else if (NodeUtil.getBooleanValue(trueNode) == TernaryValue.TRUE) { // Remove useless true case. n.detachChildren(); replacement = new Node(Token.OR, condition, falseNode); } else if (NodeUtil.getBooleanValue(falseNode) == TernaryValue.FALSE) { // Remove useless false case n.detachChildren(); replacement = new Node(Token.AND, condition, trueNode); } if (replacement != null) { parent.replaceChild(n, replacement); n = replacement; reportCodeChange(); } return n; } default: // while(true) --> while(1) TernaryValue nVal = NodeUtil.getBooleanValue(n); if (nVal != TernaryValue.UNKNOWN) { boolean result = nVal.toBoolean(true); int equivalentResult = result ? 1 : 0; return maybeReplaceChildWithNumber(n, parent, equivalentResult); } // We can't do anything else currently. return n; } } /** * Replaces a node with a number node if the new number node is not equivalent * to the current node. * * Returns the replacement for n if it was replaced, otherwise returns n. */ private Node maybeReplaceChildWithNumber(Node n, Node parent, int num) { Node newNode = Node.newNumber(num); if (!newNode.isEquivalentTo(n)) { parent.replaceChild(n, newNode); reportCodeChange(); return newNode; } return n;

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> } private static final ImmutableSet<String> STANDARD_OBJECT_CONSTRUCTORS = // String, Number, and Boolean functions return non-object types, whereas // new String, new Number, and new Boolean return object types, so don't // include them here. ImmutableSet.of( "Object", "Array", "RegExp", "Error" ); /** * Fold "new Object()" to "Object()". */ private Node tryFoldStandardConstructors(Node n) { Preconditions.checkState(n.getType() == Token.NEW); // If name normalization has been run then we know that // new Object() does in fact refer to what we think it is // and not some custom-defined Object(). if (isASTNormalized()) { if (n.getFirstChild().getType() == Token.NAME) { String className = n.getFirstChild().getString(); if (STANDARD_OBJECT_CONSTRUCTORS.contains(className)) { n.setType(Token.CALL); reportCodeChange(); } } } return n; } /** * Replaces a new Array or Object node with an object literal, unless the * call to Array or Object is to a local function with the same name. */ private Node tryFoldLiteralConstructor(Node n) { Preconditions.checkArgument(n.getType() == Token.CALL || n.getType() == Token.NEW); Node constructorNameNode = n.getFirstChild(); Node newLiteralNode = null; // We require the AST to be normalized to ensure that, say, // Object() really refers to the built-in Object constructor // and not a user-defined constructor with the same name. if (isASTNormalized() && Token.NAME == constructorNameNode.getType()) { String className = constructorNameNode.getString(); if ("RegExp".equals(className)) { // "RegExp("boo", "g")" --> /boo/g return tryFoldRegularExpressionConstructor(n); } else { boolean constructorHasArgs = constructorNameNode.getNext() != null; if ("Object".equals(className) && !constructorHasArgs) { // "Object()" --> "{}" newLiteralNode = new Node(Token.OBJECTLIT); } else if ("Array".equals(className)) { // "Array(arg0, arg1, ...)" --> "[arg0, arg1, ...]" Node arg0 = constructorNameNode.getNext(); FoldArrayAction action = isSafeToFoldArrayConstructor(arg0); if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS || action == FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS) { newLiteralNode = new Node(Token.ARRAYLIT); n.removeChildren(); if (action == FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS) { newLiteralNode.addChildrenToFront(arg0); } } } if (newLiteralNode != null) { n.getParent().replaceChild(n,

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> newLiteralNode); reportCodeChange(); return newLiteralNode; } } } return n; } private static enum FoldArrayAction { NOT_SAFE_TO_FOLD, SAFE_TO_FOLD_WITH_ARGS, SAFE_TO_FOLD_WITHOUT_ARGS} /** * Checks if it is safe to fold Array() constructor into []. It can be * obviously done, if the initial constructor has either no arguments or * at least two. The remaining case may be unsafe since Array(number) * actually reserves memory for an empty array which contains number elements. */ private FoldArrayAction isSafeToFoldArrayConstructor(Node arg) { FoldArrayAction action = FoldArrayAction.NOT_SAFE_TO_FOLD; if (arg == null) { action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS; } else if (arg.getNext() != null) { action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; } else { switch (arg.getType()) { case (Token.STRING): // "Array('a')" --> "['a']" action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; break; case (Token.NUMBER): // "Array(0)" --> "[]" if (arg.getDouble() == 0) { action = FoldArrayAction.SAFE_TO_FOLD_WITHOUT_ARGS; } break; case (Token.ARRAYLIT): // "Array([args])" --> "[[args]]" action = FoldArrayAction.SAFE_TO_FOLD_WITH_ARGS; break; default: } } return action; } private Node tryFoldRegularExpressionConstructor(Node n) { Node parent = n.getParent(); Node constructor = n.getFirstChild(); Node pattern = constructor.getNext(); // e.g. ^foobar$ Node flags = null != pattern ? pattern.getNext() : null; // e.g. gi // Only run on normalized AST to make sure RegExp() is actually // the RegExp we expect (if the AST has been normalized then // other RegExp's will have been renamed to something like RegExp$1) if (!isASTNormalized()) { return n; } if (null == pattern || (null != flags && null != flags.getNext())) { // too few or too many arguments return n; } if (// is pattern folded pattern.getType() == Token.STRING // make sure empty pattern doesn't fold to // && !"".equals(pattern.getString()) // NOTE(nicksantos): Make sure that the regexp isn't longer than // 100 chars, or it blows up the regexp parser in Opera 9.2. && pattern.getString().length() < 100 && (null == flags || flags.getType() == Token.STRING) // don't escape patterns with unicode escapes since Safari behaves badly // (read can't parse or crashes) on regex

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> literals with unicode escapes && !containsUnicodeEscape(pattern.getString())) { // Make sure that / is escaped, so that it will fit safely in /brackets/. // pattern is a string value with \\ and similar already escaped pattern = makeForwardSlashBracketSafe(pattern); Node regexLiteral; if (null == flags || "".equals(flags.getString())) { // fold to /foobar/ regexLiteral = new Node(Token.REGEXP, pattern); } else { // fold to /foobar/gi if (!areValidRegexpFlags(flags.getString())) { error(INVALID_REGULAR_EXPRESSION_FLAGS, flags); return n; } if (!areSafeFlagsToFold(flags.getString())) { return n; } n.removeChild(flags); regexLiteral = new Node(Token.REGEXP, pattern, flags); } parent.replaceChild(n, regexLiteral); reportCodeChange(); return regexLiteral; } return n; } private static final Pattern REGEXP_FLAGS_RE = Pattern.compile("^[gmi]*$"); /** * are the given flags valid regular expression flags? * Javascript recognizes several suffix flags for regular expressions, * 'g' - global replace, 'i' - case insensitive, 'm' - multi-line. * They are case insensitive, and javascript does not recognize the extended * syntax mode, single-line mode, or expression replacement mode from perl5. */ private static boolean areValidRegexpFlags(String flags) { return REGEXP_FLAGS_RE.matcher(flags).matches(); } /** * are the given flags safe to fold? * We don't fold the regular expression if global ('g') flag is on, * because in this case it isn't really a constant: its 'lastIndex' * property contains the state of last execution, so replacing * 'new RegExp('foobar','g')' with '/foobar/g' may change the behavior of * the program if the RegExp is used inside a loop, for example. */ private static boolean areSafeFlagsToFold(String flags) { return flags.indexOf('g') < 0; } /** * returns a string node that can safely be rendered inside /brackets/. */ private static Node makeForwardSlashBracketSafe(Node n) { String s = n.getString(); // sb contains everything in s[0:pos] StringBuilder sb = null; int pos = 0; for (int i = 0; i < s.length(); ++i) { switch (s.charAt(i)) { case '\\': // skip over the next char after a '\\'. ++i; break; case '/': // escape it if (null == sb) { sb = new StringBuilder(s.length() + 16); } sb.append(s, pos, i).append('\\'); pos = i; break; } } // don't discard useful line-number info if there were no changes

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Body(); indent--; endStatement(); } @Override void appendOp(String op, boolean binOp) { if (binOp) { if (getLastChar() != ' ') { append(" "); } append(op); append(" "); } else { append(op); } } /** * If the body of a for loop or the then clause of an if statement has * a single statement, should it be wrapped in a block? * {@inheritDoc} */ @Override boolean shouldPreserveExtraBlocks() { // When pretty-printing, always place the statement in its own block // so it is printed on a separate line. This allows breakpoints to be // placed on the statement. return true; } /** * @return The TRY node for the specified CATCH node. */ private Node getTryForCatch(Node n) { return n.getParent().getParent(); } /** * @return Whether the a line break should be added after the specified * BLOCK. */ @Override boolean breakAfterBlockFor(Node n, boolean isStatementContext) { Preconditions.checkState(n.getType() == Token.BLOCK); Node parent = n.getParent(); if (parent != null) { int type = parent.getType(); switch (type) { case Token.DO: // Don't break before 'while' in DO-WHILE statements. return false; case Token.FUNCTION: // FUNCTIONs are handled separately, don't break here. return false; case Token.TRY: // Don't break before catch return n != parent.getFirstChild(); case Token.CATCH: // Don't break before finally return !NodeUtil.hasFinally(getTryForCatch(parent)); case Token.IF: // Don't break before else return n == parent.getLastChild(); } } return true; } } static class CompactCodePrinter extends MappedCodePrinter { // The CompactCodePrinter tries to emit just enough newlines to stop there // being lines longer than the threshold. Since the output is going to be // gzipped, it makes sense to try to make the newlines appear in similar // contexts so that GZIP can encode them for 'free'. // // This version tries to break the lines at 'preferred' places, which are // between the top-level forms. This works because top level forms tend to // be more uniform than arbitary legal contexts. Better compression would // probably require explicit modelling of the gzip algorithm. private final boolean lineBreak; private int lineStartPosition = 0; private int preferredBreakPosition = 0; /** * @param lineBreak break the lines a bit more aggressively * @param lineLengthThreshold The length of a line after which we force * a newline when possible. * @param createSrcMap Whether to gather source position * mapping information when printing. * @param sourceMapDetailLevel A filter to control

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowEquality(p.typeB); } }; /** * Merging function for strict non-equality between types. */ private static final Function<TypePair, TypePair> SHNE = new Function<TypePair, TypePair>() { public TypePair apply(TypePair p) { if (p.typeA == null || p.typeB == null) { return null; } return p.typeA.getTypesUnderShallowInequality(p.typeB); } }; /** * Merging function for inequality comparisons between types. */ private final Function<TypePair, TypePair> INEQ = new Function<TypePair, TypePair>() { public TypePair apply(TypePair p) { return new TypePair( getRestrictedWithoutUndefined(p.typeA), getRestrictedWithoutUndefined(p.typeB)); } }; /** * Creates a semantic reverse abstract interpreter. */ SemanticReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { super(convention, typeRegistry); } public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { // Check for the typeof operator. int operatorToken = condition.getType(); switch (operatorToken) { case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: case Token.CASE: Node left; Node right; if (operatorToken == Token.CASE) { left = condition.getParent().getFirstChild(); // the switch condition right = condition.getFirstChild(); } else { left = condition.getFirstChild(); right = condition.getLastChild(); } Node typeOfNode = null; Node stringNode = null; if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) { typeOfNode = left; stringNode = right; } else if (right.getType() == Token.TYPEOF && left.getType() == Token.STRING) { typeOfNode = right; stringNode = left; } if (typeOfNode != null && stringNode != null) { Node operandNode = typeOfNode.getFirstChild(); JSType operandType = getTypeIfRefinable(operandNode, blindScope); if (operandType != null) { boolean resultEqualsValue = operatorToken == Token.EQ || operatorToken == Token.SHEQ || operatorToken == Token.CASE; if (!outcome) { resultEqualsValue = !resultEqualsValue; } return caseTypeOf(operandNode, operandType, stringNode.getString(), resultEqualsValue, blindScope); } } } switch (operatorToken) { case Token.AND: if (outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>(), condition.getLastChild(), blindScope, true); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, true); } case Token.OR: if (!outcome) { return caseAndOrNotShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } else { return caseAndOrMaybeShortCircuiting(condition.getFirstChild(), condition.getLastChild(), blindScope, false); } case Token.EQ: if (outcome) { return caseEquality(condition, blindScope, EQ); } else { return caseEquality(condition, blindScope, NE); } case Token.NE: if (outcome) { return caseEquality(condition, blindScope, NE); } else { return caseEquality(condition, blindScope, EQ); } case Token.SHEQ: if (outcome) { return caseEquality(condition, blindScope, SHEQ); } else { return caseEquality(condition, blindScope, SHNE); } case Token.SHNE: if (outcome) { return caseEquality(condition, blindScope, SHNE); } else { return caseEquality(condition, blindScope, SHEQ); } case Token.NAME: case Token.GETPROP: return caseNameOrGetProp(condition, blindScope, outcome); case Token.ASSIGN: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild().getNext(), blindScope, outcome), outcome); case Token.NOT: return firstPreciserScopeKnowingConditionOutcome( condition.getFirstChild(), blindScope, !outcome); case Token.LE: case Token.LT: case Token.GE: case Token.GT: if (outcome) { return caseEquality(condition, blindScope, INEQ); } break; case Token.INSTANCEOF: return caseInstanceOf( condition.getFirstChild(), condition.getLastChild(), blindScope, outcome); case Token.IN: if (outcome && condition.getFirstChild().getType() == Token.STRING) { return caseIn(condition.getLastChild(), condition.getFirstChild().getString(), blindScope); } break; case Token.CASE: Node left = condition.getParent().getFirstChild(); // the switch condition Node right = condition.getFirstChild(); if (outcome) { return caseEquality(left, right, blindScope, SHEQ); } else { return caseEquality(left, right, blindScope, SHNE); } } return nextPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } private FlowScope caseEquality(Node condition, FlowScope blindScope, Function<TypePair, TypePair> merging) { return caseEquality(condition.getFirstChild(), condition.getLastChild(), blindScope, merging); }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2006 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.CheckLevel; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Checks for non side effecting statements such as * <pre> * var s = "this string is " * "continued on the next line but you forgot the +"; * x == foo(); // should that be '='? * foo();; // probably just a stray-semicolon. Doesn't hurt to check though * </p> * and generates warnings. * */ final class CheckSideEffects extends AbstractPostOrderCallback { static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning( "JSC_USELESS_CODE", "Suspicious code. {0}"); private final CheckLevel level; CheckSideEffects(CheckLevel level) { this.level = level; } public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.getType() == Token.EMPTY || n.getType() == Token.COMMA) { return; } if (parent == null) return; int pt = parent.getType(); if (pt == Token.COMMA) { Node gramps = parent.getParent(); if (gramps.getType() == Token.CALL && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first // expression to a comma to be a no-op if it's used to indirect // an eval. if (n == parent.getFirstChild() && parent.getChildCount() == 2 && n.getNext().getType() == Token.NAME && "eval".equals(n.getNext().getString())) { return; } } if (

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>n == parent.getLastChild()) { for (Node an : parent.getAncestors()) { int ancestorType = an.getType(); if (ancestorType == Token.COMMA) continue; if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return; else break; } } } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) { if (pt == Token.FOR && parent.getChildCount() == 4 && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) { // Fall through and look for warnings for the 1st and 3rd child // of a for. } else { return; // it might be ok to not have a side-effect } } if (NodeUtil.isSimpleOperatorType(n.getType()) || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) { if (n.isQualifiedName() && n.getJSDocInfo() != null) { // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. return; } else if (NodeUtil.isExpressionNode(n)) { // we already reported the problem when we visited the child. return; } String msg = "This code lacks side-effects. Is there a bug?"; if (n.getType() == Token.STRING) { msg = "Is there a missing '+' on the previous line?"; } t.getCompiler().report( t.makeError(n, level, USELESS_CODE_ERROR, msg)); } } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> /** * {@inheritDoc} * * <p>This enforces Google's convention about enum key names. They must match * the regular expression {@code [A-Z0-9][A-Z0-9_]*}. * * <p>Examples: * <ul> * <li>A</li> * <li>213</li> * <li>FOO_BAR</li> * </ul> */ @Override public boolean isValidEnumKey(String key) { return ENUM_KEY_PATTERN.matcher(key).matches(); } /** * {@inheritDoc} * * <p>In Google code, parameter names beginning with {@code opt_} are * treated as optional arguments. */ @Override public boolean isOptionalParameter(Node parameter) { return parameter.getString().startsWith(OPTIONAL_ARG_PREFIX); } @Override public boolean isVarArgsParameter(Node parameter) { return VAR_ARGS_NAME.equals(parameter.getString()); } /** * {@inheritDoc} * * <p>In Google code, any global name starting with an underscore is * considered exported. */ @Override public boolean isExported(String name, boolean local) { return !local && name.startsWith("_"); } /** * {@inheritDoc} * * <p>In Google code, private names end with an underscore, and exported * names are never considered private (see {@link #isExported}). */ @Override public boolean isPrivate(String name) { return name.endsWith("_") && !isExported(name); } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp.parsing; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.javascript.jscomp.mozilla.rhino.CompilerEnvirons; import com.google.javascript.jscomp.mozilla.rhino.Context; import com.google.javascript.jscomp.mozilla.rhino.ErrorReporter; import com.google.javascript.jscomp.mozilla.rhino.EvaluatorException; import com.google.javascript.jscomp.mozilla.rhino.Parser; import com.google.javascript.jscomp.mozilla.rhino.ast.AstRoot; import com.google.javascript.rhino.Node; import java.io.IOException; import java.util.ResourceBundle; import java.util.Set; import java.util.logging.Logger; public class ParserRunner { private static final String configResource = "com.google.javascript.jscomp.parsing.ParserConfig"; private static Set<String> annotationNames = null; private static Set<String> suppressionNames = null; // Should never need to instantiate class of static methods. private ParserRunner() {} @Deprecated public static Config createConfig(boolean isIdeMode) { return createConfig(isIdeMode, false); } public static Config createConfig(boolean isIdeMode, boolean isES5Mode) { initResourceConfig(); return new Config(annotationNames, suppressionNames, isIdeMode, isES5Mode); } private static synchronized void initResourceConfig() { if (annotationNames != null) { return; } ResourceBundle config = ResourceBundle.getBundle(configResource); annotationNames = extractList(config.getString("jsdoc.annotations")); suppressionNames = extractList(config.getString("jsdoc.suppressions")); } private static Set<String> extractList(String configProp) { String[] names = configProp.split(","); Set<String> trimmedNames = Sets.newHashSet(); for (String name : names) { trimmedNames.add(name.trim()); } return ImmutableSet.copyOf(trimmedNames); } /** * Parses the JavaScript text given by a reader. * * @param sourceName The filename. * @param sourceString Source code from the

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2008 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSTypeExpression; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; /** * Prepare the AST before we do any checks or optimizations on it. * * This pass must run. It should bring the AST into a consistent state, * and add annotations where necessary. It should not make any transformations * on the tree that would lose source information, since we need that source * information for checks. * * @author johnlenz@google.com (John Lenz) */ class PrepareAst implements CompilerPass { private final AbstractCompiler compiler; private final boolean checkOnly; PrepareAst(AbstractCompiler compiler) { this(compiler, false); } PrepareAst(AbstractCompiler compiler, boolean checkOnly) { this.compiler = compiler; this.checkOnly = checkOnly; } private void reportChange() { if (checkOnly) { Preconditions.checkState(false, "normalizeNodeType constraints violated"); } } @Override public void process(Node externs, Node root) { if (checkOnly) { normalizeNodeTypes(root); } else { // Don't perform "PrepareAnnotations" when doing checks as // they currently aren't valid during sanity checks. In particular, // they DIRECT_EVAL shouldn't be applied after inlining has been // performed. if (externs != null) { NodeTraversal.traverse( compiler, externs, new PrepareAnnotations(compiler)); } if (root != null) { NodeTraversal.traverse( compiler, root, new PrepareAnnotations(compiler)); } } } /** * Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code. */ private void normalizeNodeTypes(Node n) { if (n.getType() == Token.EXPR_VOID) { n.setType(Token.EXPR_RESULT); reportChange(); } // Remove unused properties to minimize differences between ASTs // produced by the two parsers. if (n.getType() == Token.FUNCTION) { Preconditions.checkState(n.getProp(

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Node.FUNCTION_PROP) == null); } normalizeBlocks(n); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { // This pass is run during the CompilerTestCase validation, so this // parent pointer check serves as a more general check. Preconditions.checkState(child.getParent() == n); normalizeNodeTypes(child); } } /** * Add blocks to IF, WHILE, DO, etc. */ private void normalizeBlocks(Node n) { if (NodeUtil.isControlStructure(n) && n.getType() != Token.LABEL && n.getType() != Token.SWITCH) { for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (NodeUtil.isControlStructureCodeBlock(n,c) && c.getType() != Token.BLOCK) { Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno()); newBlock.copyInformationFrom(n); n.replaceChild(c, newBlock); if (c.getType() != Token.EMPTY) { newBlock.addChildrenToFront(c); } else { newBlock.setWasEmptyNode(true); } c = newBlock; reportChange(); } } } } /** * Normalize where annotations appear on the AST. Copies * around existing JSDoc annotations as well as internal annotations. */ static class PrepareAnnotations implements NodeTraversal.Callback { private final CodingConvention convention; PrepareAnnotations(AbstractCompiler compiler) { this.convention = compiler.getCodingConvention(); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.OBJECTLIT) { normalizeObjectLiteralAnnotations(n); } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.CALL: annotateCalls(n); break; case Token.FUNCTION: annotateFunctions(n, parent); annotateDispatchers(n, parent); break; } } private void normalizeObjectLiteralAnnotations(Node objlit) { Preconditions.checkState(objlit.getType() == Token.OBJECTLIT); for (Node key = objlit.getFirstChild(); key != null; key = key.getNext()) { Node value = key.getFirstChild(); normalizeObjectLiteralKeyAnnotations(objlit, key, value); } } /** * There are two types of calls we are interested in calls without explicit * "this" values (what we are call "free" calls) and direct call to eval. */ private void annotateCalls(Node n) { Preconditions.checkState(n.getType() == Token.CALL); // Keep track of of the "this" context of a call. A call without an // explicit "this" is a free

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> call. Node first = n.getFirstChild(); if (!NodeUtil.isGet(first)) { n.putBooleanProp(Node.FREE_CALL, true); } // Keep track of the context in which eval is called. It is important // to distinguish between "(0, eval)()" and "eval()". if (first.getType() == Token.NAME && "eval".equals(first.getString())) { first.putBooleanProp(Node.DIRECT_EVAL, true); } } /** * Translate dispatcher info into the property expected node. */ private void annotateDispatchers(Node n, Node parent) { Preconditions.checkState(n.getType() == Token.FUNCTION); if (parent.getJSDocInfo() != null && parent.getJSDocInfo().isJavaDispatch()) { if (parent.getType() == Token.ASSIGN) { Preconditions.checkState(parent.getLastChild() == n); n.putBooleanProp(Node.IS_DISPATCHER, true); } } } /** * In the AST that Rhino gives us, it needs to make a distinction * between jsdoc on the object literal node and jsdoc on the object literal * value. For example, * <pre> * var x = { * / JSDOC / * a: 'b', * c: / JSDOC / 'd' * }; * </pre> * * But in few narrow cases (in particular, function literals), it's * a lot easier for us if the doc is attached to the value. */ private void normalizeObjectLiteralKeyAnnotations( Node objlit, Node key, Node value) { Preconditions.checkState(objlit.getType() == Token.OBJECTLIT); if (key.getJSDocInfo() != null && value.getType() == Token.FUNCTION) { value.setJSDocInfo(key.getJSDocInfo()); } } /** * Annotate optional and var_arg function parameters. */ private void annotateFunctions(Node n, Node parent) { JSDocInfo fnInfo = NodeUtil.getFunctionInfo(n); // Compute which function parameters are optional and // which are var_args. Node args = n.getFirstChild().getNext(); for (Node arg = args.getFirstChild(); arg != null; arg = arg.getNext()) { String argName = arg.getString(); JSTypeExpression typeExpr = fnInfo == null ? null : fnInfo.getParameterType(argName); if (convention.isOptionalParameter(arg) || typeExpr != null && typeExpr.isOptionalArg()) { arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true); } if (convention.isVarArgsParameter(arg) || typeExpr != null && typeExpr.isVarArgs()) { arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true); } } } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> } /** * Expect that the first type can be cast to the second type. The first type * should be either a subtype or supertype of the second. * * @param t The node traversal. * @param n The node where warnings should point. * @param type The type being cast from. * @param castType The type being cast to. */ void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) { castType = castType.restrictByNotNullOrUndefined(); type = type.restrictByNotNullOrUndefined(); if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) { if (shouldReport) { compiler.report( t.makeError(n, INVALID_CAST, castType.toString(), type.toString())); } registerMismatch(type, castType); } } /** * Expect that the given variable has not been declared with a type. * * @param sourceName The name of the source file we're in. * @param n The node where warnings should point to. * @param parent The parent of {@code n}. * @param var The variable that we're checking. * @param variableName The name of the variable. * @param newType The type being applied to the variable. Mostly just here * for the benefit of the warning. */ void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var, String variableName, JSType newType) { boolean allowDupe = false; if (n.getType() == Token.GETPROP) { JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); } JSType varType = var.getType(); // Only report duplicate declarations that have types. Other duplicates // will be reported by the syntactic scope creator later in the // compilation process. if (varType != null && varType != typeRegistry.getNativeType(UNKNOWN_TYPE) && newType != null && newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) { // If there are two typed declarations of the same variable, that // is an error and the second declaration is ignored, except in the // case of native types. A null input type means that the declaration // was made in TypedScopeCreator#createInitialScope and is a // native type. if (var.input == null) { n.setJSType(varType); if (parent.getType() == Token.VAR) { if (n.getFirstChild() != null) { n.getFirstChild().setJSType(varType); } } else { Preconditions.checkState(parent.getType() == Token.FUNCTION); parent.setJSType(varType); } } else { // Always

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> warn about duplicates if the overridden type does not // match the original type. // // If the types match, suppress the warning iff there was a @suppress // tag, or if the original declaration was a stub. if (!(allowDupe || var.getParentNode().getType() == Token.EXPR_RESULT) || !newType.equals(varType)) { if (shouldReport) { compiler.report( JSError.make(sourceName, n, DUP_VAR_DECLARATION, variableName, newType.toString(), var.getInputName(), String.valueOf(var.nameNode.getLineno()), varType.toString())); } } } } } /** * Expect that all properties on interfaces that this type implements are * implemented. */ void expectAllInterfacePropertiesImplemented(FunctionType type) { ObjectType instance = type.getInstanceType(); for (ObjectType implemented : type.getAllImplementedInterfaces()) { if (implemented.getImplicitPrototype() != null) { for (String prop : implemented.getImplicitPrototype().getOwnPropertyNames()) { if (!instance.hasProperty(prop)) { Node source = type.getSource(); Preconditions.checkNotNull(source); String sourceName = (String) source.getProp(Node.SOURCENAME_PROP); sourceName = sourceName == null ? "" : sourceName; if (shouldReport) { compiler.report(JSError.make(sourceName, source, INTERFACE_METHOD_NOT_IMPLEMENTED, prop, implemented.toString(), instance.toString())); } registerMismatch(instance, implemented); } } } } } /** * Report a type mismatch */ private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSType required) { mismatch(t.getSourceName(), n, msg, found, required); } private void mismatch(NodeTraversal t, Node n, String msg, JSType found, JSTypeNative required) { mismatch(t, n, msg, found, getNativeType(required)); } private void mismatch(String sourceName, Node n, String msg, JSType found, JSType required) { registerMismatch(found, required); if (shouldReport) { compiler.report( JSError.make(sourceName, n, TYPE_MISMATCH_WARNING, formatFoundRequired(msg, found, required))); } } private void registerMismatch(JSType found, JSType required) { // Don't register a mismatch for differences in null or undefined or if the // code didn't downcast. found = found.restrictByNotNullOrUndefined(); required = required.restrictByNotNullOrUndefined(); if (found.canAssignTo(required) || required.canAssignTo(found)) { return; } mismatches.add(new TypeMismatch(found, required)); if (found instanceof FunctionType && required instanceof FunctionType) { FunctionType fnTypeA = ((FunctionType) found); FunctionType fnTypeB

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> = ((FunctionType) required); Iterator<Node> paramItA = fnTypeA.getParameters().iterator(); Iterator<Node> paramItB = fnTypeB.getParameters().iterator(); while (paramItA.hasNext() && paramItB.hasNext()) { registerIfMismatch(paramItA.next().getJSType(), paramItB.next().getJSType()); } registerIfMismatch(fnTypeA.getReturnType(), fnTypeB.getReturnType()); } } private void registerIfMismatch(JSType found, JSType required) { if (found != null && required != null && !found.canAssignTo(required)) { registerMismatch(found, required); } } /** * Formats a found/required error message. */ private String formatFoundRequired(String description, JSType found, JSType required) { return MessageFormat.format(FOUND_REQUIRED, description, found, required); } /** * Given a node, get a human-readable name for the type of that node so * that will be easy for the programmer to find the original declaration. * * For example, if SubFoo's property "bar" might have the human-readable * name "Foo.prototype.bar". * * @param n The node. * @param dereference If true, the type of the node will be dereferenced * to an Object type, if possible. */ String getReadableJSTypeName(Node n, boolean dereference) { // If we're analyzing a GETPROP, the property may be inherited by the // prototype chain. So climb the prototype chain and find out where // the property was originally defined. if (n.getType() == Token.GETPROP) { ObjectType objectType = getJSType(n.getFirstChild()).dereference(); if (objectType != null) { String propName = n.getLastChild().getString(); while (objectType != null && !objectType.hasOwnProperty(propName)) { objectType = objectType.getImplicitPrototype(); } // Don't show complex function names or anonymous types. // Instead, try to get a human-readable type name. if (objectType != null && (objectType.getConstructor() != null || objectType.isFunctionPrototypeType())) { return objectType.toString() + "." + propName; } } } JSType type = getJSType(n); if (dereference) { ObjectType dereferenced = type.dereference(); if (dereferenced != null) { type = dereferenced; } } String qualifiedName = n.getQualifiedName(); if (type.isFunctionPrototypeType() || (type.toObjectType() != null && type.toObjectType().getConstructor() != null)) { return type.toString(); } else if (qualifiedName != null) { return qualifiedName; } else if (type instanceof FunctionType) { // Don't show complex function names. return "function"; } else {

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> = Maps.newHashMap(); assignableDefines = Maps.newHashMap(); assignAllowed = new ArrayDeque<Integer>(); assignAllowed.push(1); // Create a map of references to defines keyed by node for easy lookup allRefInfo = Maps.newHashMap(); for (Name name : listOfDefines) { if (name.declaration != null) { allRefInfo.put(name.declaration.node, new RefInfo(name.declaration, name)); } if (name.refs != null) { for (Ref ref : name.refs) { // If there's a TWIN def, only put one of the twins in. if (ref.getTwin() == null || !ref.getTwin().isSet()) { allRefInfo.put(ref.node, new RefInfo(ref, name)); } } } } } /** * Get a map of {@link DefineInfo} structures, keyed by the name of * the define. */ Map<String, DefineInfo> getAllDefines() { return allDefines; } /** * Keeps track of whether the traversal is in a conditional branch. * We traverse all nodes of the parse tree. */ public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { updateAssignAllowedStack(n, true); return true; } public void visit(NodeTraversal t, Node n, Node parent) { RefInfo refInfo = allRefInfo.get(n); if (refInfo != null) { Ref ref = refInfo.ref; Name name = refInfo.name; String fullName = name.fullName(); switch (ref.type) { case SET_FROM_GLOBAL: case SET_FROM_LOCAL: Node valParent = getValueParent(ref); Node val = valParent.getLastChild(); if (valParent.getType() == Token.ASSIGN && name.isSimpleName() && name.declaration == ref) { // For defines, it's an error if a simple name is assigned // before it's declared compiler.report( t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName)); } else if (processDefineAssignment(t, fullName, val, valParent)) { // remove the assignment so that the variable is still declared, // but no longer assigned to a value, e.g., // DEF_FOO = 5; // becomes "5;" // We can't remove the ASSIGN/VAR when we're still visiting its // children, so we'll have to come back later to remove it. refInfo.name.removeRef(ref); lvalueToRemoveLater = valParent; } break; default: if (t.inGlobalScope()) { // Treat this as a reference to a define in the global scope. // After this point, the define must not be reassigned, // or it's an error. DefineInfo info = assignableDefines.get(fullName); if (info != null

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>) { setDefineInfoNotAssignable(info, t); assignableDefines.remove(fullName); } } break; } } if (!t.inGlobalScope() && n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) { // warn about @define annotations in local scopes compiler.report( t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, "")); } if (lvalueToRemoveLater == n) { lvalueToRemoveLater = null; if (n.getType() == Token.ASSIGN) { Node last = n.getLastChild(); n.removeChild(last); parent.replaceChild(n, last); } else { Preconditions.checkState(n.getType() == Token.NAME); n.removeChild(n.getFirstChild()); } compiler.reportCodeChange(); } if (n.getType() == Token.CALL) { if (t.inGlobalScope()) { // If there's a function call in the global scope, // we just say it's unsafe and freeze all the defines. // // NOTE(nicksantos): We could be a lot smarter here. For example, // ReplaceOverriddenVars keeps a call graph of all functions and // which functions/variables that they reference, and tries // to statically determine which functions are "safe" and which // are not. But this would be overkill, expecially because // the intended use of defines is with config_files, where // all the defines are at the top of the bundle. for (DefineInfo info : assignableDefines.values()) { setDefineInfoNotAssignable(info, t); } assignableDefines.clear(); } } updateAssignAllowedStack(n, false); } /** * Determines whether assignment to a define should be allowed * in the subtree of the given node, and if not, records that fact. * * @param n The node whose subtree we're about to enter or exit. * @param entering True if we're entering the subtree, false otherwise. */ private void updateAssignAllowedStack(Node n, boolean entering) { switch (n.getType()) { case Token.CASE: case Token.FOR: case Token.FUNCTION: case Token.HOOK: case Token.IF: case Token.SWITCH: case Token.WHILE: if (entering) { assignAllowed.push(0); } else { assignAllowed.remove(); } break; } } /** * Determines whether assignment to a define should be allowed * at the current point of the traversal. */ private boolean isAssignAllowed() { return assignAllowed.element() == 1; } /** * Tracks the given define. * * @param t The current traversal, for context. * @param name The full name for this define. * @param value The value assigned to the define. * @param valueParent The parent node of

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> no warning is expected */ public void testSame(String[] js, DiagnosticType error, DiagnosticType warning) { test(js, js, error, warning); } /** * Verifies that the compiler pass's JS output is the same as the input. * * @param modules Module inputs */ public void testSame(JSModule[] modules) { testSame(modules, null); } /** * Verifies that the compiler pass's JS output is the same as the input. * * @param modules Module inputs * @param warning A warning, or null for no expected warning. */ public void testSame(JSModule[] modules, DiagnosticType warning) { try { String[] expected = new String[modules.length]; for (int i = 0; i < modules.length; i++) { expected[i] = ""; for (CompilerInput input : modules[i].getInputs()) { expected[i] += input.getSourceFile().getCode(); } } test(modules, expected, null, warning); } catch (IOException e) { throw new RuntimeException(e); } } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param compiler A compiler that has been initialized via * {@link Compiler#init} * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected */ protected void test(Compiler compiler, String[] expected, DiagnosticType error, DiagnosticType warning) { test(compiler, expected, error, warning, null); } /** * Verifies that the compiler pass's JS output matches the expected output * and (optionally) that an expected warning is issued. Or, if an error is * expected, this method just verifies that the error is encountered. * * @param compiler A compiler that has been initialized via * {@link Compiler#init} * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, * or null if no warning is expected or if the warning's description * should not be examined */ private void test(Compiler compiler, String[] expected, DiagnosticType error, DiagnosticType warning, String description) { RecentChange recentChange = new RecentChange(); compiler.addChangeHandler(recentChange); Node root = compiler.parseInputs(); assertTrue("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root != null); Node externsRoot = root.getFirstChild(); Node mainRoot = root.getLastChild(); // Save the tree

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> for later comparison. Node rootClone = root.cloneTree(); Node externsRootClone = rootClone.getFirstChild(); Node mainRootClone = rootClone.getLastChild(); int numRepetitions = getNumRepetitions(); ErrorManager[] errorManagers = new ErrorManager[numRepetitions]; int aggregateWarningCount = 0; List<JSError> aggregateWarnings = Lists.newArrayList(); boolean hasCodeChanged = false; assertFalse("Code should not change before processing", recentChange.hasCodeChanged()); for (int i = 0; i < numRepetitions; ++i) { if (compiler.getErrorCount() == 0) { errorManagers[i] = new BlackHoleErrorManager(compiler); // Only run the type checking pass once, if asked. // Running it twice can cause unpredictable behavior because duplicate // objects for the same type are created, and the type system // uses reference equality to compare many types. if (typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } // Only run the normalize pass once, if asked. if (normalizeEnabled && i == 0) { normalizeActualCode(compiler, externsRoot, mainRoot); } if (markNoSideEffects && i == 0) { MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler); mark.process(externsRoot, mainRoot); } recentChange.reset(); getProcessor(compiler).process(externsRoot, mainRoot); if (checkLineNumbers) { (new LineNumberCheck(compiler)).process(externsRoot, mainRoot); } hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); aggregateWarningCount += errorManagers[i].getWarningCount(); aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings())); if (normalizeEnabled) { boolean verifyDeclaredConstants = true; new Normalize.VerifyConstants(compiler, verifyDeclaredConstants) .process(externsRoot, mainRoot); } } } if (error == null) { assertEquals( "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()), 0, compiler.getErrorCount()); // Verify the symbol table. ErrorManager symbolTableErrorManager = new BlackHoleErrorManager(compiler); Node expectedRoot = parseExpectedJs(expected); expectedRoot.detachFromParent(); JSError[] stErrors = symbolTableErrorManager.getErrors(); if (expectedSymbolTableError != null) { assertEquals("There should be one error.", 1, stErrors.length); assertEquals(expectedSymbolTableError, stErrors[0].getType()); } else { assertEquals("Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors), 0, stErrors.length); } if (warning == null) { assertEquals( "Unexpected warning

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>(s): " + Joiner.on("\n").join(aggregateWarnings), 0, aggregateWarningCount); } else { assertEquals("There should be one warning, repeated " + numRepetitions + " time(s).", numRepetitions, aggregateWarningCount); for (int i = 0; i < numRepetitions; ++i) { JSError[] warnings = errorManagers[i].getWarnings(); JSError actual = warnings[0]; assertEquals(warning, actual.getType()); // Make sure that source information is always provided. if (!allowSourcelessWarnings) { assertTrue("Missing source file name in warning", actual.sourceName != null && !actual.sourceName.isEmpty()); assertTrue("Missing line number in warning", -1 != actual.lineNumber); assertTrue("Missing char number in warning", -1 != actual.getCharno()); } if (description != null) { assertEquals(description, actual.description); } } } if (normalizeEnabled) { normalizeActualCode(compiler, externsRootClone, mainRootClone); } if (mainRootClone.checkTreeEqualsSilent(mainRoot)) { assertFalse( "compiler.reportCodeChange() was called " + "even though nothing changed", hasCodeChanged); } else { assertTrue("compiler.reportCodeChange() should have been called", hasCodeChanged); } if (compareAsTree) { String explanation = expectedRoot.checkTreeEquals(mainRoot); assertNull("\nExpected: " + compiler.toSource(expectedRoot) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } else if (expected != null) { assertEquals( Joiner.on("").join(expected), compiler.toSource(mainRoot)); } // Verify normalization is not invalidated. Node normalizeCheckRootClone = root.cloneTree(); Node normalizeCheckExternsRootClone = root.getFirstChild(); Node normalizeCheckMainRootClone = root.getLastChild(); new PrepareAst(compiler).process( normalizeCheckExternsRootClone, normalizeCheckMainRootClone); String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull("Node structure normalization invalidated.\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); // TODO(johnlenz): enable this for most test cases. // Currently, this invalidates test for while-loops, for-loop // initializers, and other naming. However, a set of code // (FoldConstants, etc) runs before the Normalize pass, so this can't be // force on everywhere. if (normalizeEnabled) { new Normalize(compiler, true).process( normalizeCheckExternsRootClone, normalizeCheckMainRootClone); explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot);

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> = null; // Vars that still need to be declared in externs. These will be declared // at the end of the pass, or when we see the equivalent var declared // in the normal code. private Set<String> varsToDeclareInExterns = Sets.newHashSet(); private final AbstractCompiler compiler; // Whether this is the post-processing sanity check. private final boolean sanityCheck; // Whether extern checks emit error. private boolean strictExternCheck; VarCheck(AbstractCompiler compiler) { this(compiler, false); } VarCheck(AbstractCompiler compiler, boolean sanityCheck) { this.compiler = compiler; this.strictExternCheck = compiler.getErrorLevel( JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR; this.sanityCheck = sanityCheck; } @Override public void process(Node externs, Node root) { // Don't run externs-checking in sanity check mode. Normalization will // remove duplicate VAR declarations, which will make // externs look like they have assigns. if (!sanityCheck) { NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck()); } NodeTraversal.traverseRoots( compiler, Lists.newArrayList(externs, root), this); for (String varName : varsToDeclareInExterns) { createSynthesizedExternVar(varName); } } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() != Token.NAME) { return; } String varName = n.getString(); // Only a function can have an empty name. if (varName.isEmpty()) { Preconditions.checkState(NodeUtil.isFunction(parent)); // A function declaration with an empty name passes Rhino, // but is supposed to be a syntax error according to the spec. if (!NodeUtil.isFunctionExpression(parent)) { t.report(n, INVALID_FUNCTION_DECL); } return; } // Check if this is a declaration for a var that has been declared // elsewhere. If so, mark it as a duplicate. if ((parent.getType() == Token.VAR || NodeUtil.isFunctionDeclaration(parent)) && varsToDeclareInExterns.contains(varName)) { createSynthesizedExternVar(varName); parent.addSuppression("duplicate"); } // Check that the var has been declared. Scope scope = t.getScope(); Scope.Var var = scope.getVar(varName); if (var == null) { if (NodeUtil.isFunctionExpression(parent)) { // e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the // current scope. } else { // The extern checks are stricter, don't report a second error. if (!strictExternCheck || !t.getInput().isExtern()) { t.report(

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> extends AbstractPostOrderCallback { public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { switch (parent.getType()) { case Token.VAR: case Token.FUNCTION: case Token.LP: // These are okay. break; case Token.GETPROP: if (n == parent.getFirstChild()) { Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString()); varsToDeclareInExterns.add(n.getString()); } } break; default: t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString()); Scope scope = t.getScope(); Scope.Var var = scope.getVar(n.getString()); if (var == null) { varsToDeclareInExterns.add(n.getString()); } break; } } } } /** Lazily create a "new" externs input for undeclared variables. */ private CompilerInput getSynthesizedExternsInput() { if (synthesizedExternsInput == null) { synthesizedExternsInput = compiler.newExternInput("{SyntheticVarsDeclar}"); } return synthesizedExternsInput; } /** Lazily create a "new" externs root for undeclared variables. */ private Node getSynthesizedExternsRoot() { if (synthesizedExternsRoot == null) { CompilerInput synthesizedExterns = getSynthesizedExternsInput(); synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler); } return synthesizedExternsRoot; } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> uses reference equality, so it's necessary to * inject a scope when you traverse. */ ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior, Predicate<Var> varFilter) { this.compiler = compiler; this.behavior = behavior; this.varFilter = varFilter; } /** * Convenience method for running this pass over a tree with this * class as a callback. */ public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } /** * Gets the variables that were referenced in this callback. */ public Set<Var> getReferencedVariables() { return referenceMap.keySet(); } /** * Gets the reference collection for the given variable. */ public ReferenceCollection getReferenceCollection(Var v) { return referenceMap.get(v); } /** * For each node, update the block stack and reference collection * as appropriate. */ public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.NAME) { Var v = t.getScope().getVar(n.getString()); if (v != null && varFilter.apply(v)) { addReference(t, v, new Reference(n, parent, t, blockStack.peek())); } } if (isBlockBoundary(n, parent)) { blockStack.pop(); } } /** * Updates block stack and invokes any additional behavior. */ public void enterScope(NodeTraversal t) { Node n = t.getScope().getRootNode(); BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek(); blockStack.push(new BasicBlock(parent, n)); } /** * Updates block statck and invokes any additional behavior. */ public void exitScope(NodeTraversal t) { blockStack.pop(); behavior.afterExitScope(t, referenceMap); } /** * Updates block stack. */ public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { // If node is a new basic block, put on basic block stack if (isBlockBoundary(n, parent)) { blockStack.push(new BasicBlock(blockStack.peek(), n)); } return true; } /** * @return true if this node marks the start of a new basic block */ private static boolean isBlockBoundary(Node n, Node parent) { if (parent != null) { switch (parent.getType()) { case Token.DO: case Token.FOR: case Token.TRY: case Token.WHILE: case Token.WITH: // NOTE: TRY has up to 3 child blocks: // TRY // BLOCK // BLOCK // CATCH // BLOCK // Note that there is an explcit CATCH token but no explicit // FINALLY token. For simplicity, we consider each BLOCK // a separate basic BLOCK. return true;

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> * Constructs an interpreter, which is the only link in a chain. Interpreters * can be appended using {@link #append}. */ ChainableReverseAbstractInterpreter(CodingConvention convention, JSTypeRegistry typeRegistry) { Preconditions.checkNotNull(convention); this.convention = convention; this.typeRegistry = typeRegistry; firstLink = this; nextLink = null; } /** * Appends a link to {@code this}, returning the updated last link. * <p> * The pattern {@code new X().append(new Y())...append(new Z())} forms a * chain starting with X, then Y, then ... Z. * @param lastLink a chainable interpreter, with no next link * @return the updated last link */ ChainableReverseAbstractInterpreter append( ChainableReverseAbstractInterpreter lastLink) { Preconditions.checkArgument(lastLink.nextLink == null); this.nextLink = lastLink; lastLink.firstLink = this.firstLink; return lastLink; } /** * Gets the first link of this chain. */ ChainableReverseAbstractInterpreter getFirst() { return firstLink; } /** * Calculates the preciser scope starting with the first link. */ protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return firstLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome); } /** * Delegates the calculation of the preciser scope to the next link. * If there is no next link, returns the blind scope. */ protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition, FlowScope blindScope, boolean outcome) { return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome( condition, blindScope, outcome) : blindScope; } /** * Returns the type of a node in the given scope if the node corresponds to a * name whose type is capable of being refined. * @return The current type of the node if it can be refined, null otherwise. */ JSType getTypeIfRefinable(Node node, FlowScope scope) { switch (node.getType()) { case Token.NAME: StaticSlot<JSType> nameVar = scope.getSlot(node.getString()); if (nameVar != null) { JSType nameVarType = nameVar.getType(); if (nameVarType == null) { nameVarType = node.getJSType(); } return nameVarType; } return null; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); if (qualifiedName == null) { return null; } StaticSlot<JSType> propVar = scope.getSlot(qualifiedName); JSType propVarType = null; if (propVar != null) { propVarType = propVar.getType(); } if (propVarType == null)

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> { propVarType = node.getJSType(); } if (propVarType == null) { propVarType = getNativeType(UNKNOWN_TYPE); } return propVarType; } return null; } /** * Declares a refined type in {@code scope} for the name represented by * {@code node}. It must be possible to refine the type of the given node in * the given scope, as determined by {@link #getTypeIfRefinable}. */ protected void declareNameInScope(FlowScope scope, Node node, JSType type) { switch (node.getType()) { case Token.NAME: scope.inferSlotType(node.getString(), type); break; case Token.GETPROP: String qualifiedName = node.getQualifiedName(); Preconditions.checkNotNull(qualifiedName); JSType origType = node.getJSType(); origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType; scope.inferQualifiedSlot(qualifiedName, origType, type); break; default: throw new IllegalArgumentException("Node cannot be refined. \n" + node.toStringTree()); } } /** * @see #getRestrictedWithoutUndefined(JSType) */ private final Visitor<JSType> restrictUndefinedVisitor = new Visitor<JSType>() { public JSType caseEnumElementType(EnumElementType enumElementType) { JSType type = enumElementType.getPrimitiveType().visit(this); if (type != null && enumElementType.getPrimitiveType().equals(type)) { return enumElementType; } else { return type; } } public JSType caseAllType() { return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE); } public JSType caseNoObjectType() { return getNativeType(NO_OBJECT_TYPE); } public JSType caseNoType() { return getNativeType(NO_TYPE); } public JSType caseBooleanType() { return getNativeType(BOOLEAN_TYPE); } public JSType caseFunctionType(FunctionType type) { return type; } public JSType caseNullType() { return getNativeType(NULL_TYPE); } public JSType caseNumberType() { return getNativeType(NUMBER_TYPE); } public JSType caseObjectType(ObjectType type) { return type; } public JSType caseStringType() { return getNativeType(STRING_TYPE); } public JSType caseUnionType(UnionType type) { return type.getRestrictedUnion(getNativeType(VOID_TYPE)); } public JSType caseUnknownType() { return getNativeType(UNKNOWN_TYPE); } public JSType caseVoidType() { return null; } }; /** * @see #getRestrictedWithoutNull(JSType) */ private final Visitor<JSType> restrictNullVisitor = new Visitor<JSType

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> boolean enterSection) { switch (n.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.VAR: case Token.FUNCTION: case Token.ASSIGN: JSDocInfo info = n.getJSDocInfo(); if (info != null && info.isNoTypeCheck()) { if (enterSection) { noTypeCheckSection++; } else { noTypeCheckSection--; } } validator.setShouldReport(noTypeCheckSection == 0); break; } } private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType, String... arguments) { if (noTypeCheckSection == 0) { t.report(n, diagnosticType, arguments); } } public boolean shouldTraverse( NodeTraversal t, Node n, Node parent) { checkNoTypeCheckSection(n, true); switch (n.getType()) { case Token.FUNCTION: // normal type checking final TypeCheck outerThis = this; final Scope outerScope = t.getScope(); final FunctionType functionType = (FunctionType) n.getJSType(); final String functionPrivateName = n.getFirstChild().getString(); if (functionPrivateName != null && functionPrivateName.length() > 0 && outerScope.isDeclared(functionPrivateName, false) && // Ideally, we would want to check whether the type in the scope // differs from the type being defined, but then the extern // redeclarations of built-in types generates spurious warnings. !(outerScope.getVar( functionPrivateName).getType() instanceof FunctionType)) { report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName); } // TODO(user): Only traverse the function's body. The function's // name and arguments are traversed by the scope creator, and ideally // should not be traversed by the type checker. break; } return true; } /** * This is the meat of the type checking. It is basically one big switch, * with each case representing one type of parse tree node. The individual * cases are usually pretty straightforward. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. */ public void visit(NodeTraversal t, Node n, Node parent) { JSType childType; JSType leftType, rightType; Node left, right; // To be explicitly set to false if the node is not typeable. boolean typeable = true; switch (n.getType()) { case Token.NAME: typeable = visitName(t, n, parent); break; case Token.LP: // If this is under a FUNCTION node, it is a parameter list and can be // ignored here. if (parent.getType() != Token.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>FUNCTION) { ensureTyped(t, n, getJSType(n.getFirstChild())); } else { typeable = false; } break; case Token.COMMA: ensureTyped(t, n, getJSType(n.getLastChild())); break; case Token.TRUE: case Token.FALSE: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.THIS: ensureTyped(t, n, t.getScope().getTypeOfThis()); break; case Token.REF_SPECIAL: ensureTyped(t, n); break; case Token.GET_REF: ensureTyped(t, n, getJSType(n.getFirstChild())); break; case Token.NULL: ensureTyped(t, n, NULL_TYPE); break; case Token.NUMBER: if (n.getParent().getType() != Token.OBJECTLIT) { ensureTyped(t, n, NUMBER_TYPE); } else { typeable = false; } break; case Token.ARRAYLIT: ensureTyped(t, n, ARRAY_TYPE); break; case Token.STRING: if (n.getParent().getType() != Token.OBJECTLIT) { ensureTyped(t, n, STRING_TYPE); } else { typeable = false; } break; case Token.REGEXP: ensureTyped(t, n, REGEXP_TYPE); break; case Token.GETPROP: visitGetProp(t, n, parent); typeable = !(parent.getType() == Token.ASSIGN && parent.getFirstChild() == n); break; case Token.GETELEM: visitGetElem(t, n); // The type of GETELEM is always unknown, so no point counting that. // If that unknown leaks elsewhere (say by an assignment to another // variable), then it will be counted. typeable = false; break; case Token.VAR: visitVar(t, n); typeable = false; break; case Token.NEW: visitNew(t, n); typeable = true; break; case Token.CALL: visitCall(t, n); typeable = !NodeUtil.isExpressionNode(parent); break; case Token.RETURN: visitReturn(t, n); typeable = false; break; case Token.DEC: case Token.INC: left = n.getFirstChild(); validator.expectNumber( t, left, getJSType(left), "increment/decrement"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.NOT: ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.VOID: ensureTyped(t, n, VOID_TYPE); break; case Token.TYPEOF: ensureTyped(t, n, STRING_TYPE); break; case Token.BITNOT: childType = getJSType(n.getFirstChild());

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> if (!childType.matchesInt32Context()) { report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()), childType.toString()); } ensureTyped(t, n, NUMBER_TYPE); break; case Token.POS: case Token.NEG: left = n.getFirstChild(); validator.expectNumber(t, left, getJSType(left), "sign operator"); ensureTyped(t, n, NUMBER_TYPE); break; case Token.EQ: case Token.NE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); TernaryValue result = leftTypeRestricted.testForEquality(rightTypeRestricted); if (result != TernaryValue.UNKNOWN) { if (n.getType() == Token.NE) { result = result.not(); } report(t, n, DETERMINISTIC_TEST, leftType.toString(), rightType.toString(), result.toString()); } ensureTyped(t, n, BOOLEAN_TYPE); break; } case Token.SHEQ: case Token.SHNE: { leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined(); JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined(); if (!leftTypeRestricted.canTestForShallowEqualityWith( rightTypeRestricted)) { report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(), rightType.toString()); } ensureTyped(t, n, BOOLEAN_TYPE); break; } case Token.LT: case Token.LE: case Token.GT: case Token.GE: leftType = getJSType(n.getFirstChild()); rightType = getJSType(n.getLastChild()); if (rightType.isNumber()) { validator.expectNumber( t, n, leftType, "left side of numeric comparison"); } else if (leftType.isNumber()) { validator.expectNumber( t, n, rightType, "right side of numeric comparison"); } else if (leftType.matchesNumberContext() && rightType.matchesNumberContext()) { // OK. } else { // Whether the comparison is numeric will be determined at runtime // each time the expression is evaluated. Regardless, both operands // should match a string context. String message = "left side of comparison"; validator.expectString(t, n, leftType, message); validator.expectNotVoid( t, n, leftType, message, getNativeType(STRING_TYPE)); message = "right side of comparison"; validator.expectString(t, n, rightType, message

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>); validator.expectNotVoid( t, n, rightType, message, getNativeType(STRING_TYPE)); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.IN: left = n.getFirstChild(); right = n.getLastChild(); leftType = getJSType(left); rightType = getJSType(right); validator.expectObject(t, n, rightType, "'in' requires an object"); validator.expectString(t, left, leftType, "left side of 'in'"); ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.INSTANCEOF: left = n.getFirstChild(); right = n.getLastChild(); leftType = getJSType(left); rightType = getJSType(right).restrictByNotNullOrUndefined(); validator.expectAnyObject( t, left, leftType, "deterministic instanceof yields false"); validator.expectActualObject( t, right, rightType, "instanceof requires an object"); ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.ASSIGN: visitAssign(t, n); typeable = false; break; case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_SUB: case Token.ASSIGN_ADD: case Token.ASSIGN_MUL: case Token.LSH: case Token.RSH: case Token.URSH: case Token.DIV: case Token.MOD: case Token.BITOR: case Token.BITXOR: case Token.BITAND: case Token.SUB: case Token.ADD: case Token.MUL: visitBinaryOperator(n.getType(), t, n); break; case Token.DELPROP: if (!isReference(n.getFirstChild())) { report(t, n, BAD_DELETE); } ensureTyped(t, n, BOOLEAN_TYPE); break; case Token.CASE: JSType switchType = getJSType(parent.getFirstChild()); JSType caseType = getJSType(n.getFirstChild()); validator.expectSwitchMatchesCase(t, n, switchType, caseType); typeable = false; break; case Token.WITH: { Node child = n.getFirstChild(); childType = getJSType(child); validator.expectObject( t, child, childType, "with requires an object"); typeable = false; break; } case Token.FUNCTION: visitFunction(t, n); break; // These nodes have no interesting type behavior. case Token.LABEL: case Token.LABEL_NAME: case Token.SWITCH: case Token.BREAK: case Token.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>CATCH: case Token.TRY: case Token.SCRIPT: case Token.EXPR_RESULT: case Token.BLOCK: case Token.EMPTY: case Token.DEFAULT: case Token.CONTINUE: case Token.DEBUGGER: case Token.THROW: typeable = false; break; // These nodes require data flow analysis. case Token.DO: case Token.FOR: case Token.IF: case Token.WHILE: typeable = false; break; // These nodes are typed during the type inference. case Token.AND: case Token.HOOK: case Token.OBJECTLIT: case Token.OR: if (n.getJSType() != null) { // If we didn't run type inference. ensureTyped(t, n); } else { // If this is an enum, then give that type to the objectlit as well. if ((n.getType() == Token.OBJECTLIT) && (parent.getJSType() instanceof EnumType)) { ensureTyped(t, n, parent.getJSType()); } else { ensureTyped(t, n); } } break; default: report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType())); ensureTyped(t, n); break; } // Don't count externs since the user's code may not even use that part. typeable = typeable && !inExterns; if (typeable) { doPercentTypedAccounting(t, n); } checkNoTypeCheckSection(n, false); } /** * Counts the given node in the typed statistics. * @param n a node that should be typed */ private void doPercentTypedAccounting(NodeTraversal t, Node n) { JSType type = n.getJSType(); if (type == null) { nullCount++; } else if (type.isUnknownType()) { if (reportUnknownTypes.isOn()) { compiler.report( t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE)); } unknownCount++; } else { typedCount++; } } /** * Visits an assignment <code>lvalue = rvalue</code>. If the * <code>lvalue</code> is a prototype modification, we change the schema * of the object type it is referring to. * @param t the traversal * @param assign the assign node * (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant) */ private void visitAssign(NodeTraversal t, Node assign) { JSDocInfo info = assign.getJSDocInfo(); Node lvalue = assign.getFirstChild(); Node rvalue = assign.getLastChild(); if (lvalue.getType() == Token.GETPROP) { Node object = lvalue.getFirstChild(); JSType objectJsType = getJSType(object); String property = lvalue.getLastChild().getString

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>(); // the first name in this getprop refers to an interface // we perform checks in addition to the ones below if (object.getType() == Token.GETPROP) { JSType jsType = getJSType(object.getFirstChild()); if (jsType.isInterface() && object.getLastChild().getString().equals("prototype")) { visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue); } } // /** @type ... */object.name = ...; if (info != null && info.hasType()) { visitAnnotatedAssignGetprop(t, assign, info.getType().evaluate(t.getScope(), typeRegistry), object, property, rvalue); return; } // /** @enum ... */object.name = ...; if (info != null && info.hasEnumParameterType()) { checkEnumInitializer( t, rvalue, info.getEnumParameterType().evaluate( t.getScope(), typeRegistry)); return; } // object.prototype = ...; if (property.equals("prototype")) { if (objectJsType instanceof FunctionType) { FunctionType functionType = (FunctionType) objectJsType; if (functionType.isConstructor()) { JSType rvalueType = rvalue.getJSType(); validator.expectObject(t, rvalue, rvalueType, OVERRIDING_PROTOTYPE_WITH_NON_OBJECT); } } else { // TODO(user): might want to flag that } return; } // object.prototype.property = ...; if (object.getType() == Token.GETPROP) { Node object2 = object.getFirstChild(); String property2 = NodeUtil.getStringValue(object.getLastChild()); if ("prototype".equals(property2)) { JSType jsType = object2.getJSType(); if (jsType instanceof FunctionType) { FunctionType functionType = (FunctionType) jsType; if (functionType.isConstructor() || functionType.isInterface()) { checkDeclaredPropertyInheritance( t, assign, functionType, property, info, getJSType(rvalue)); } } else { // TODO(user): might want to flag that } return; } } // object.property = ...; ObjectType type = ObjectType.cast( objectJsType.restrictByNotNullOrUndefined()); if (type != null) { if (type.hasProperty(property) && !type.isPropertyTypeInferred(property) && !propertyIsImplicitCast(type, property)) { validator.expectCanAssignToPropertyOf( t, assign, getJSType(rvalue), type.getPropertyType(property), object, property); } return; } } else if (lvalue.getType() == Token.NAME) { // variable with inferred type case JSType rvalueType = getJSType(assign.getLastChild()); Var var = t.getScope().getVar(lvalue.getString()); if (var

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> != null) { if (var.isTypeInferred()) { return; } } } // fall through case JSType leftType = getJSType(lvalue); Node rightChild = assign.getLastChild(); JSType rightType = getJSType(rightChild); if (validator.expectCanAssignTo( t, assign, rightType, leftType, "assignment")) { ensureTyped(t, assign, rightType); } else { ensureTyped(t, assign); } } /** * Returns true if any type in the chain has an implictCast annotation for * the given property. */ private boolean propertyIsImplicitCast(ObjectType type, String prop) { for (; type != null; type = type.getImplicitPrototype()) { JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop); if (docInfo != null && docInfo.isImplicitCast()) { return true; } } return false; } /** * Given a constructor type and a property name, check that the property has * the JSDoc annotation @override iff the property is declared on a * superclass. Several checks regarding inheritance correctness are also * performed. */ private void checkDeclaredPropertyInheritance( NodeTraversal t, Node n, FunctionType ctorType, String propertyName, JSDocInfo info, JSType propertyType) { // TODO(user): We're not 100% confident that type-checking works, // so we return quietly if the unknown type is a superclass of this type. // Remove this check as we become more confident. We should flag a warning // when the unknown type is on the inheritance chain, as it is likely // because of a programmer error. if (ctorType.hasUnknownSupertype()) { return; } FunctionType superClass = ctorType.getSuperClassConstructor(); boolean superClassHasProperty = superClass != null && superClass.getPrototype().hasProperty(propertyName); boolean declaredOverride = info != null && info.isOverride(); boolean foundInterfaceProperty = false; if (ctorType.isConstructor()) { for (JSType implementedInterface : ctorType.getImplementedInterfaces()) { if (implementedInterface.isUnknownType()) { continue; } FunctionType interfaceType = implementedInterface.toObjectType().getConstructor(); boolean interfaceHasProperty = interfaceType.getPrototype().hasProperty(propertyName); foundInterfaceProperty = foundInterfaceProperty || interfaceHasProperty; if (reportMissingOverride.isOn() && !declaredOverride && interfaceHasProperty) { // @override not present, but the property does override an interface // property compiler.report(t.makeError(n, reportMissingOverride, HIDDEN_INTERFACE_PROPERTY, propertyName, interfaceType.getTopMostDefiningType(propertyName).toString())); } // Check that it is ok if (interfaceHasProperty) { JSType interfacePropType = interfaceType.getPrototype().getPropertyType(propertyName); if (!propertyType.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>canAssignTo(interfacePropType)) { compiler.report(t.makeError(n, HIDDEN_INTERFACE_PROPERTY_MISMATCH, propertyName, interfaceType.getTopMostDefiningType(propertyName).toString(), interfacePropType.toString(), propertyType.toString())); } } } } if (!declaredOverride && !superClassHasProperty) { // nothing to do here, it's just a plain new property return; } JSType topInstanceType = superClassHasProperty ? superClass.getTopMostDefiningType(propertyName) : null; if (reportMissingOverride.isOn() && ctorType.isConstructor() && !declaredOverride && superClassHasProperty) { // @override not present, but the property does override a superclass // property compiler.report(t.makeError(n, reportMissingOverride, HIDDEN_SUPERCLASS_PROPERTY, propertyName, topInstanceType.toString())); } if (!declaredOverride) { // there's no @override to check return; } // @override is present and we have to check that it is ok if (superClassHasProperty) { // there is a superclass implementation JSType superClassPropType = superClass.getPrototype().getPropertyType(propertyName); if (!propertyType.canAssignTo(superClassPropType)) { compiler.report( t.makeError(n, HIDDEN_SUPERCLASS_PROPERTY_MISMATCH, propertyName, topInstanceType.toString(), superClassPropType.toString(), propertyType.toString())); } } else if (!foundInterfaceProperty) { // there is no superclass nor interface implementation compiler.report( t.makeError(n, UNKNOWN_OVERRIDE, propertyName, ctorType.getInstanceType().toString())); } } /** * Visits an ASSIGN node for cases such as * <pre> * interface.property2.property = ...; * </pre> */ private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object, String property, Node lvalue, Node rvalue) { JSType rvalueType = getJSType(rvalue); // Only 2 values are allowed for methods: // goog.abstractMethod // function () {}; // or for properties, no assignment such as: // InterfaceFoo.prototype.foobar; String abstractMethodName = compiler.getCodingConvention().getAbstractMethodName(); if (!rvalueType.isOrdinaryFunction() && !(rvalue.isQualifiedName() && rvalue.getQualifiedName().equals(abstractMethodName))) { // This is bad i18n style but we don't localize our compiler errors. String abstractMethodMessage = (abstractMethodName != null) ? ", or " + abstractMethodName : ""; compiler.report( t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION, abstractMethodMessage)); } if (assign.getLastChild().getType() == Token.FUNCTION && !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) { compiler.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>report( t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY, abstractMethodName)); } } /** * Visits an ASSIGN node for cases such as * <pre> * object.property = ...; * </pre> * that have an {@code @type} annotation. */ private void visitAnnotatedAssignGetprop(NodeTraversal t, Node assign, JSType type, Node object, String property, Node rvalue) { // verifying that the rvalue has the correct type validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type, object, property); } /** * Visits a NAME node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of the node n. * @return whether the node is typeable or not */ boolean visitName(NodeTraversal t, Node n, Node parent) { // At this stage, we need to determine whether this is a leaf // node in an expression (which therefore needs to have a type // assigned for it) versus some other decorative node that we // can safely ignore. Function names, arguments (children of LP nodes) and // variable declarations are ignored. // TODO(user): remove this short-circuiting in favor of a // pre order traversal of the FUNCTION, CATCH, LP and VAR nodes. int parentNodeType = parent.getType(); if (parentNodeType == Token.FUNCTION || parentNodeType == Token.CATCH || parentNodeType == Token.LP || parentNodeType == Token.VAR) { return false; } JSType type = n.getJSType(); if (type == null) { type = getNativeType(UNKNOWN_TYPE); Var var = t.getScope().getVar(n.getString()); if (var != null) { JSType varType = var.getType(); if (varType != null) { type = varType; } } } ensureTyped(t, n, type); return true; } /** * Visits a GETPROP node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. * @param parent The parent of <code>n</code> */ private void visitGetProp(NodeTraversal t, Node n, Node parent) { // GETPROP nodes have an assigned type on their node by the scope creator // if this is an enum declaration. The only namespaced enum declarations // that we allow are of the form object.name = ...; if (n.getJSType() != null && parent.getType() == Token.ASSIGN) { return; } // obj.prop or obj.method() // Lots of types can appear

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> on the left, a call to a void function can // never be on the left. getPropertyType will decide what is acceptable // and what isn't. Node property = n.getLastChild(); Node objNode = n.getFirstChild(); JSType childType = getJSType(objNode); // TODO(user): remove in favor of flagging every property access on // non-object. if (!validator.expectNotVoid(t, n, childType, "undefined has no properties", getNativeType(OBJECT_TYPE))) { ensureTyped(t, n); return; } checkPropertyAccess(childType, property.getString(), t, n); ensureTyped(t, n); } /** * Make sure that the access of this property is ok. */ private void checkPropertyAccess(JSType childType, String propName, NodeTraversal t, Node n) { ObjectType objectType = childType.dereference(); if (objectType != null) { JSType propType = getJSType(n); if ((!objectType.hasProperty(propName) || objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) && propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) { if (objectType instanceof EnumType) { report(t, n, INEXISTENT_ENUM_ELEMENT, propName); } else if (!objectType.isEmptyType() && reportMissingProperties && !isPropertyTest(n)) { if (!typeRegistry.canPropertyBeDefined(objectType, propName)) { report(t, n, INEXISTENT_PROPERTY, propName, validator.getReadableJSTypeName(n.getFirstChild(), true)); } } } } else { // TODO(nicksantos): might want to flag the access on a non object when // it's impossible to get a property from this type. } } /** * Determines whether this node is testing for the existence of a property. * If true, we will not emit warnings about a missing property. * * @param getProp The GETPROP being tested. */ private boolean isPropertyTest(Node getProp) { Node parent = getProp.getParent(); switch (parent.getType()) { case Token.CALL: return parent.getFirstChild() != getProp && compiler.getCodingConvention().isPropertyTestFunction(parent); case Token.IF: case Token.WHILE: case Token.DO: case Token.FOR: return NodeUtil.getConditionExpression(parent) == getProp; case Token.INSTANCEOF: case Token.TYPEOF: return true; case Token.AND: case Token.HOOK: return parent.getFirstChild() == getProp; case Token.NOT: return parent.getParent().getType() == Token.OR && parent.getParent().getFirstChild() == parent; } return false; } /** * Visits a GETELEM node. * * @param t The

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitGetElem(NodeTraversal t, Node n) { Node left = n.getFirstChild(); Node right = n.getLastChild(); validator.expectIndexMatch(t, n, getJSType(left), getJSType(right)); ensureTyped(t, n); } /** * Visits a VAR node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitVar(NodeTraversal t, Node n) { // TODO(nicksantos): Fix this so that the doc info always shows up // on the NAME node. We probably want to wait for the parser // merge to fix this. JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null; for (Node name : n.children()) { Node value = name.getFirstChild(); // A null var would indicate a bug in the scope creation logic. Var var = t.getScope().getVar(name.getString()); if (value != null) { JSType valueType = getJSType(value); JSType nameType = var.getType(); nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType; JSDocInfo info = name.getJSDocInfo(); if (info == null) { info = varInfo; } if (info != null && info.hasEnumParameterType()) { // var.getType() can never be null, this would indicate a bug in the // scope creation logic. checkEnumInitializer( t, value, info.getEnumParameterType().evaluate(t.getScope(), typeRegistry)); } else if (var.isTypeInferred()) { ensureTyped(t, name, valueType); } else { validator.expectCanAssignTo( t, value, valueType, nameType, "initializing variable"); } } } } /** * Visits a NEW node. */ private void visitNew(NodeTraversal t, Node n) { Node constructor = n.getFirstChild(); FunctionType type = getFunctionType(constructor); if (type != null && type.isConstructor()) { visitParameterList(t, n, type); ensureTyped(t, n, type.getInstanceType()); } else { // TODO(user): add support for namespaced objects. if (constructor.getType() != Token.GETPROP) { // TODO(user): make the constructor node have lineno/charno // and use constructor for a more precise error indication. // It seems that GETPROP nodes are missing this information. Node line; if (constructor.getLineno() < 0 || constructor.getCharno()

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> < 0) { line = n; } else { line = constructor; } report(t, line, NOT_A_CONSTRUCTOR); } ensureTyped(t, n); } } /** * Visits a {@link Token#FUNCTION} node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitFunction(NodeTraversal t, Node n) { JSDocInfo info = n.getJSDocInfo(); FunctionType functionType = (FunctionType) n.getJSType(); String functionPrivateName = n.getFirstChild().getString(); if (functionType.isInterface() || functionType.isConstructor()) { FunctionType baseConstructor = functionType. getPrototype().getImplicitPrototype().getConstructor(); if (baseConstructor != null && baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) && (baseConstructor.isConstructor() && functionType.isInterface() || baseConstructor.isInterface() && functionType.isConstructor())) { compiler.report( t.makeError(n, CONFLICTING_EXTENDED_TYPE, functionPrivateName)); } for (JSType baseInterface : functionType.getImplementedInterfaces()) { boolean badImplementedType = false; ObjectType baseInterfaceObj = ObjectType.cast(baseInterface); if (baseInterfaceObj != null) { FunctionType interfaceConstructor = baseInterfaceObj.getConstructor(); if (interfaceConstructor != null && !interfaceConstructor.isInterface()) { badImplementedType = true; } } else { badImplementedType = true; } if (badImplementedType) { report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName); } } if (functionType.isConstructor()) { validator.expectAllInterfacePropertiesImplemented(functionType); } } } /** * Visits a CALL node. * * @param t The node traversal object that supplies context, such as the * scope chain to use in name lookups as well as error reporting. * @param n The node being visited. */ private void visitCall(NodeTraversal t, Node n) { Node child = n.getFirstChild(); JSType childType = getJSType(child).restrictByNotNullOrUndefined(); if (!childType.canBeCalled()) { report(t, n, NOT_CALLABLE, childType.toString()); ensureTyped(t, n); return; } // A couple of types can be called as if they were functions. // If it is a function type, then validate parameters. if (childType instanceof FunctionType) { FunctionType functionType = (FunctionType) childType; // Non-native constructors should never be called directly. if (functionType.isConstructor() && !functionType.isNativeObjectType()) { report(t, n, CONSTRUCTOR_NOT

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>) { actualReturnType = getNativeType(VOID_TYPE); valueNode = n; } else { actualReturnType = getJSType(valueNode); } // verifying validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType, "inconsistent return type"); } } /** * This function unifies the type checking involved in the core binary * operators and the corresponding assignment operators. The representation * used internally is such that common code can handle both kinds of * operators easily. * * @param op The operator. * @param t The traversal object, needed to report errors. * @param n The node being checked. */ private void visitBinaryOperator(int op, NodeTraversal t, Node n) { Node left = n.getFirstChild(); JSType leftType = getJSType(left); Node right = n.getLastChild(); JSType rightType = getJSType(right); switch (op) { case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.LSH: case Token.RSH: case Token.ASSIGN_URSH: case Token.URSH: if (!leftType.matchesInt32Context()) { report(t, left, BIT_OPERATION, NodeUtil.opToStr(n.getType()), leftType.toString()); } if (!rightType.matchesUint32Context()) { report(t, right, BIT_OPERATION, NodeUtil.opToStr(n.getType()), rightType.toString()); } break; case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_MUL: case Token.ASSIGN_SUB: case Token.DIV: case Token.MOD: case Token.MUL: case Token.SUB: validator.expectNumber(t, left, leftType, "left operand"); validator.expectNumber(t, right, rightType, "right operand"); break; case Token.ASSIGN_BITAND: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITOR: case Token.BITAND: case Token.BITXOR: case Token.BITOR: validator.expectBitwiseable(t, left, leftType, "bad left operand to bitwise operator"); validator.expectBitwiseable(t, right, rightType, "bad right operand to bitwise operator"); break; case Token.ASSIGN_ADD: case Token.ADD: break; default: report(t, n, UNEXPECTED_TOKEN, Node.tokenToName(op)); } ensureTyped(t, n); } /** * <p>Checks the initializer of an enum. An enum can be initialized with an * object literal whose values must be subtypes of the declared enum element * type, or by copying another enum.</p> * * <p>In the case of an enum copy, we verify that the enum element type of the * enum

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> used for initialization is a subtype of the enum element type of * the enum the value is being copied in.</p> * * <p>Examples:</p> * <pre>var myEnum = {FOO: ..., BAR: ...}; * var myEnum = myOtherEnum;</pre> * * @param value the value used for initialization of the enum * @param primitiveType The type of each element of the enum. */ private void checkEnumInitializer( NodeTraversal t, Node value, JSType primitiveType) { if (value.getType() == Token.OBJECTLIT) { for (Node key = value.getFirstChild(); key != null; key = key.getNext()) { Node propValue = key.getFirstChild(); // the value's type must be assignable to the enum's primitive type validator.expectCanAssignTo( t, propValue, getJSType(propValue), primitiveType, "element type must match enum's type"); } } else if (value.getJSType() instanceof EnumType) { // TODO(user): Remove the instanceof check in favor // of a type.isEnumType() predicate. Currently, not all enum types are // implemented by the EnumClass, e.g. the unknown type and the any // type. The types need to be defined by interfaces such that an // implementation can implement multiple types interface. EnumType valueEnumType = (EnumType) value.getJSType(); JSType valueEnumPrimitiveType = valueEnumType.getElementsType().getPrimitiveType(); validator.expectCanAssignTo(t, value, valueEnumPrimitiveType, primitiveType, "incompatible enum element types"); } else { // The error condition is handled in TypedScopeCreator. } } /** * This predicate is used to determine if the node represents an expression * that is a Reference according to JavaScript definitions. * * @param n The node being checked. * @return true if the sub-tree n is a reference, false otherwise. */ private static boolean isReference(Node n) { switch (n.getType()) { case Token.GETELEM: case Token.GETPROP: case Token.NAME: return true; default: return false; } } /** * This method gets the JSType from the Node argument and verifies that it is * present. */ private JSType getJSType(Node n) { JSType jsType = n.getJSType(); if (jsType == null) { // TODO(nicksantos): This branch indicates a compiler bug, not worthy of // halting the compilation but we should log this and analyze to track // down why it happens. This is not critical and will be resolved over // time as the type checker is extended. return getNativeType(UNKNOWN_TYPE); } else { return jsType; } } /** * Gets the type of the node or {@code null} if the node's type is not a * function. */ private Function

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Type getFunctionType(Node n) { JSType type = getJSType(n).restrictByNotNullOrUndefined(); if (type.isUnknownType()) { return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE); } else if (type instanceof FunctionType) { return (FunctionType) type; } else { return null; } } // TODO(nicksantos): TypeCheck should never be attaching types to nodes. // All types should be attached by TypeInference. This is not true today // for legacy reasons. There are a number of places where TypeInference // doesn't attach a type, as a signal to TypeCheck that it needs to check // that node's type. /** * Ensure that the given node has a type. If it does not have one, * attach the UNKNOWN_TYPE. */ private void ensureTyped(NodeTraversal t, Node n) { ensureTyped(t, n, getNativeType(UNKNOWN_TYPE)); } private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) { ensureTyped(t, n, getNativeType(type)); } /** * Enforces type casts, and ensures the node is typed. * * A cast in the way that we use it in JSDoc annotations never * alters the generated code and therefore never can induce any runtime * operation. What this means is that a 'cast' is really just a compile * time constraint on the underlying value. In the future, we may add * support for run-time casts for compiled tests. * * To ensure some shred of sanity, we enforce the notion that the * type you are casting to may only meaningfully be a narrower type * than the underlying declared type. We also invalidate optimizations * on bad type casts. * * @param t The traversal object needed to report errors. * @param n The node getting a type assigned to it. * @param type The type to be assigned. */ private void ensureTyped(NodeTraversal t, Node n, JSType type) { // Make sure FUNCTION nodes always get function type. Preconditions.checkState(n.getType() != Token.FUNCTION || type instanceof FunctionType || type.isUnknownType()); JSDocInfo info = n.getJSDocInfo(); if (info != null) { if (info.hasType()) { JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry); validator.expectCanCast(t, n, infoType, type); type = infoType; } if (info.isImplicitCast() && !inExterns) { String propName = n.getType() == Token.GETPROP ? n.getLastChild().getString() : "(missing)"; compiler.report( t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName)); } } if (n.getJSType() == null) { n.setJSType

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2010 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.Node; /** * Look for references to the global RegExp object that would cause * regular expressions to be unoptimizable. * * @author johnlenz@google.com (John Lenz) */ class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass { static final DiagnosticType REGEXP_REFERENCE = DiagnosticType.warning("JSC_REGEXP_REFERENCE", "References to the global RegExp object prevents " + "optimization of regular expressions."); private final AbstractCompiler compiler; private boolean globalRegExpPropertiesUsed = false; public boolean isGlobalRegExpPropertiesUsed() { return globalRegExpPropertiesUsed; } public CheckRegExp(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isReferenceName(n)) { String name = n.getString(); if (name.equals("RegExp") && t.getScope().getVar(name) == null) { int parentType = parent.getType(); boolean first = (n == parent.getFirstChild()); if (!((parentType == Token.NEW && first) || (parentType == Token.CALL && first) || (parentType == Token.INSTANCEOF && !first))) { t.report(n, REGEXP_REFERENCE); globalRegExpPropertiesUsed = true; } } } } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>(child); skipIndexes[j] = i; j++; } i++; } node.putProp(Node.SKIP_INDEXES_PROP, skipIndexes); } return node; } @Override Node processAssignment(Assignment assignmentNode) { return processInfixExpression(assignmentNode); } @Override Node processAstRoot(AstRoot rootNode) { Node node = newNode(Token.SCRIPT); for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) { node.addChildToBack(transform((AstNode)child)); } parseDirectives(node); return node; } /** * Parse the directives, encode them in the AST, and remove their nodes. * * For information on ES5 directives, see section 14.1 of * Ecma-262, Edition 5. * * It would be nice if Rhino would eventually take care of this for * us, but right now their directive-processing is a one-off. */ private void parseDirectives(Node node) { // Remove all the directives, and encode them in the AST. Set<String> directives = null; while (isDirective(node.getFirstChild())) { String directive = node.removeFirstChild().getFirstChild().getString(); if (directives == null) { directives = Sets.newHashSet(directive); } else { directives.add(directive); } } if (directives != null) { node.setDirectives(directives); } } private boolean isDirective(Node n) { if (n == null) return false; int nType = n.getType(); return (nType == Token.EXPR_RESULT || nType == Token.EXPR_VOID) && n.getFirstChild().getType() == Token.STRING && ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString()); } @Override Node processBlock(Block blockNode) { return processGeneric(blockNode); } @Override Node processBreakStatement(BreakStatement statementNode) { Node node = newNode(Token.BREAK); if (statementNode.getBreakLabel() != null) { Node labelName = transform(statementNode.getBreakLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processCatchClause(CatchClause clauseNode) { AstNode catchVar = clauseNode.getVarName(); Node node = newNode(Token.CATCH, transform(catchVar)); if (clauseNode.getCatchCondition() != null) { errorReporter.error( "Catch clauses are not supported", sourceName, clauseNode.getCatchCondition().getLineno(), "", 0); } node.addChildToBack(transformBlock(clauseNode.getBody())); return node; } @Override Node processConditionalExpression(ConditionalExpression exprNode)

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> { return newNode( Token.HOOK, transform(exprNode.getTestExpression()), transform(exprNode.getTrueExpression()), transform(exprNode.getFalseExpression())); } @Override Node processContinueStatement(ContinueStatement statementNode) { Node node = newNode(Token.CONTINUE); if (statementNode.getLabel() != null) { Node labelName = transform(statementNode.getLabel()); // Change the NAME to LABEL_NAME labelName.setType(Token.LABEL_NAME); node.addChildToBack(labelName); } return node; } @Override Node processDoLoop(DoLoop loopNode) { return newNode( Token.DO, transformBlock(loopNode.getBody()), transform(loopNode.getCondition())); } @Override Node processElementGet(ElementGet getNode) { return newNode( Token.GETELEM, transform(getNode.getTarget()), transform(getNode.getElement())); } @Override Node processEmptyExpression(EmptyExpression exprNode) { Node node = newNode(Token.EMPTY); return node; } @Override Node processExpressionStatement(ExpressionStatement statementNode) { Node node = newNode(transformTokenType(statementNode.getType())); node.addChildToBack(transform(statementNode.getExpression())); return node; } @Override Node processForInLoop(ForInLoop loopNode) { return newNode( Token.FOR, transform(loopNode.getIterator()), transform(loopNode.getIteratedObject()), transformBlock(loopNode.getBody())); } @Override Node processForLoop(ForLoop loopNode) { Node node = newNode( Token.FOR, transform(loopNode.getInitializer()), transform(loopNode.getCondition()), transform(loopNode.getIncrement())); node.addChildToBack(transformBlock(loopNode.getBody())); return node; } @Override Node processFunctionCall(FunctionCall callNode) { Node node = newNode(transformTokenType(callNode.getType()), transform(callNode.getTarget())); for (AstNode child : callNode.getArguments()) { node.addChildToBack(transform(child)); } int leftParamPos = callNode.getAbsolutePosition() + callNode.getLp(); node.setLineno(callNode.getLineno()); node.setCharno(position2charno(leftParamPos)); return node; } @Override Node processFunctionNode(FunctionNode functionNode) { Name name = functionNode.getFunctionName(); Boolean isUnnamedFunction = false; if (name == null) { name = new Name(); name.setIdentifier(""); isUnnamedFunction = true; } Node node = newNode(Token.FUNCTION); Node newName = transform(name); if (isUnnamedFunction) { // Old Rhino tagged the empty name node with the line number of the // declaration. newName.setLineno(functionNode.getLineno()); // TODO(bowdidge) Mark

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Node.getName()); } @Override Node processLabeledStatement(LabeledStatement statementNode) { Node node = newNode(Token.LABEL); Node prev = null; Node cur = node; for (Label label : statementNode.getLabels()) { if (prev != null) { prev.addChildToBack(cur); } cur.addChildToBack(transform(label)); cur.setLineno(label.getLineno()); int clauseAbsolutePosition = position2charno(label.getAbsolutePosition()); cur.setCharno(clauseAbsolutePosition); prev = cur; cur = newNode(Token.LABEL); } prev.addChildToBack(transform(statementNode.getStatement())); return node; } @Override Node processName(Name nameNode) { return newStringNode(Token.NAME, nameNode.getIdentifier()); } @Override Node processNewExpression(NewExpression exprNode) { return processFunctionCall(exprNode); } @Override Node processNumberLiteral(NumberLiteral literalNode) { return newNumberNode(literalNode.getNumber()); } @Override Node processObjectLiteral(ObjectLiteral literalNode) { if (literalNode.isDestructuring()) { reportDestructuringAssign(literalNode); } Node node = newNode(Token.OBJECTLIT); for (ObjectProperty el : literalNode.getElements()) { if (!config.acceptES5) { if (el.isGetter()) { reportGetter(el); continue; } else if (el.isSetter()) { reportSetter(el); continue; } } Node key = transformAsString(el.getLeft()); if (el.isGetter()) { key.setType(Token.GET); } else if (el.isSetter()) { key.setType(Token.SET); } key.addChildToFront(transform(el.getRight())); node.addChildToBack(key); } return node; } @Override Node processObjectProperty(ObjectProperty propertyNode) { return processInfixExpression(propertyNode); } @Override Node processParenthesizedExpression(ParenthesizedExpression exprNode) { Node node = transform(exprNode.getExpression()); node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE); return node; } @Override Node processPropertyGet(PropertyGet getNode) { return newNode( Token.GETPROP, transform(getNode.getTarget()), transformAsString(getNode.getProperty())); } @Override Node processRegExpLiteral(RegExpLiteral literalNode) { Node literalStringNode = newStringNode(literalNode.getValue()); // assume it's on the same line. literalStringNode.setLineno(literalNode.getLineno()); Node node = newNode(Token.REGEXP, literalStringNode); String flags = literalNode.getFlags(); if (flags != null && !flags.isEmpty()) { Node flagsNode = newStringNode(flags); // Assume the flags

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> return Token.NOT; case com.google.javascript.jscomp.mozilla.rhino.Token.BITNOT: return Token.BITNOT; case com.google.javascript.jscomp.mozilla.rhino.Token.POS: return Token.POS; case com.google.javascript.jscomp.mozilla.rhino.Token.NEG: return Token.NEG; case com.google.javascript.jscomp.mozilla.rhino.Token.NEW: return Token.NEW; case com.google.javascript.jscomp.mozilla.rhino.Token.DELPROP: return Token.DELPROP; case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOF: return Token.TYPEOF; case com.google.javascript.jscomp.mozilla.rhino.Token.GETPROP: return Token.GETPROP; case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP: return Token.SETPROP; case com.google.javascript.jscomp.mozilla.rhino.Token.GETELEM: return Token.GETELEM; case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM: return Token.SETELEM; case com.google.javascript.jscomp.mozilla.rhino.Token.CALL: return Token.CALL; case com.google.javascript.jscomp.mozilla.rhino.Token.NAME: return Token.NAME; case com.google.javascript.jscomp.mozilla.rhino.Token.NUMBER: return Token.NUMBER; case com.google.javascript.jscomp.mozilla.rhino.Token.STRING: return Token.STRING; case com.google.javascript.jscomp.mozilla.rhino.Token.NULL: return Token.NULL; case com.google.javascript.jscomp.mozilla.rhino.Token.THIS: return Token.THIS; case com.google.javascript.jscomp.mozilla.rhino.Token.FALSE: return Token.FALSE; case com.google.javascript.jscomp.mozilla.rhino.Token.TRUE: return Token.TRUE; case com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ: return Token.SHEQ; case com.google.javascript.jscomp.mozilla.rhino.Token.SHNE: return Token.SHNE; case com.google.javascript.jscomp.mozilla.rhino.Token.REGEXP: return Token.REGEXP; case com.google.javascript.jscomp.mozilla.rhino.Token.BINDNAME: return Token.BINDNAME; case com.google.javascript.jscomp.mozilla.rhino.Token.THROW: return Token.THROW; case com.google.javascript.jscomp.mozilla.rhino.Token.RETHROW: return Token.RETHROW; case com.google.javascript.jscomp.mozilla.rhino.Token.IN:

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>ino.Token.RESERVED: return Token.RESERVED; case com.google.javascript.jscomp.mozilla.rhino.Token.EMPTY: return Token.EMPTY; case com.google.javascript.jscomp.mozilla.rhino.Token.BLOCK: return Token.BLOCK; case com.google.javascript.jscomp.mozilla.rhino.Token.LABEL: return Token.LABEL; case com.google.javascript.jscomp.mozilla.rhino.Token.TARGET: return Token.TARGET; case com.google.javascript.jscomp.mozilla.rhino.Token.LOOP: return Token.LOOP; case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_VOID: case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_RESULT: return Token.EXPR_RESULT; case com.google.javascript.jscomp.mozilla.rhino.Token.JSR: return Token.JSR; case com.google.javascript.jscomp.mozilla.rhino.Token.SCRIPT: return Token.SCRIPT; case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOFNAME: return Token.TYPEOFNAME; case com.google.javascript.jscomp.mozilla.rhino.Token.USE_STACK: return Token.USE_STACK; case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP_OP: return Token.SETPROP_OP; case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM_OP: return Token.SETELEM_OP; case com.google.javascript.jscomp.mozilla.rhino.Token.LOCAL_BLOCK: return Token.LOCAL_BLOCK; case com.google.javascript.jscomp.mozilla.rhino.Token.SET_REF_OP: return Token.SET_REF_OP; case com.google.javascript.jscomp.mozilla.rhino.Token.DOTDOT: return Token.DOTDOT; case com.google.javascript.jscomp.mozilla.rhino.Token.COLONCOLON: return Token.COLONCOLON; case com.google.javascript.jscomp.mozilla.rhino.Token.XML: return Token.XML; case com.google.javascript.jscomp.mozilla.rhino.Token.DOTQUERY: return Token.DOTQUERY; case com.google.javascript.jscomp.mozilla.rhino.Token.XMLATTR: return Token.XMLATTR; case com.google.javascript.jscomp.mozilla.rhino.Token.XMLEND: return Token.XMLEND; case com.google.javascript.jscomp.mozilla.rhino.Token.TO_OBJECT: return Token.TO_OBJECT; case com.google.javascript.jscomp.mozilla.rhino.Token.TO_DOUBLE: return Token.TO_DOUBLE; case com.google.javascript.js

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>parent.getString() == v.name) { continue; } // Only generate warnings if the scopes do not match in order // to deal with possible forward declarations and recursion if (reference.getScope() == v.scope) { compiler.report( JSError.make(reference.getSourceName(), reference.getNameNode(), checkLevel, UNDECLARED_REFERENCE, v.name)); } } if (isDeclaration) { blocksWithDeclarations.add(basicBlock); isDeclaredInScope = true; } } } } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> inputsByName.get(name); } @Override public CompilerInput newExternInput(String name) { if (inputsByName.containsKey(name)) { throw new IllegalArgumentException("Conflicting externs name: " + name); } SourceAst ast = new SyntheticAst(name); CompilerInput input = new CompilerInput(ast, name, true); inputsByName.put(name, input); externsRoot.addChildToFront(ast.getAstRoot(this)); return input; } /** Add a source input dynamically. Intended for incremental compilation. */ void addIncrementalSourceAst(JsAst ast) { String sourceName = ast.getSourceFile().getName(); Preconditions.checkState( getInput(sourceName) == null, "Duplicate input of name " + sourceName); inputsByName.put(sourceName, new CompilerInput(ast)); } /** * Replace a source input dynamically. Intended for incremental * re-compilation. * * If the new source input doesn't parse, then keep the old input * in the AST and return false. * * @return Whether the new AST was attached successfully. */ boolean replaceIncrementalSourceAst(JsAst ast) { String sourceName = ast.getSourceFile().getName(); CompilerInput oldInput = Preconditions.checkNotNull( getInput(sourceName), "No input to replace: " + sourceName); Node newRoot = ast.getAstRoot(this); if (newRoot == null) { return false; } Node oldRoot = oldInput.getAstRoot(this); if (oldRoot != null) { oldRoot.getParent().replaceChild(oldRoot, newRoot); } else { getRoot().getLastChild().addChildToBack(newRoot); } inputsByName.put(sourceName, new CompilerInput(ast)); return true; } @Override JSModuleGraph getModuleGraph() { return moduleGraph; } @Override public JSTypeRegistry getTypeRegistry() { if (typeRegistry == null) { typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes); } return typeRegistry; } @Override ScopeCreator getScopeCreator() { return getPassConfig().getScopeCreator(); } @Override public Scope getTopScope() { return getPassConfig().getTopScope(); } @Override public ReverseAbstractInterpreter getReverseAbstractInterpreter() { if (abstractInterpreter == null) { ChainableReverseAbstractInterpreter interpreter = new SemanticReverseAbstractInterpreter( getCodingConvention(), getTypeRegistry()); if (options.closurePass) { interpreter = new ClosureReverseAbstractInterpreter( getCodingConvention(), getTypeRegistry()) .append(interpreter).getFirst(); } abstractInterpreter = interpreter; } return abstractInterpreter; } @Override TypeValidator getTypeValidator() { if (typeValidator == null) { typeValidator = new TypeValidator(this); } return typeValidator; } //------------------------------------------------------------------------ // Parsing //

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Node.getNext(); final Node body = args.getNext(); // Bleed the function name into the scope, if it hasn't // been declared in the outer scope. String fnName = fnNameNode.getString(); if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) { declareVar(fnNameNode); } // Args: Declare function variables Preconditions.checkState(args.getType() == Token.LP); for (Node a = args.getFirstChild(); a != null; a = a.getNext()) { Preconditions.checkState(a.getType() == Token.NAME); declareVar(a); } // Body scanVars(body, n); } else { // It's the global block Preconditions.checkState(scope.getParent() == null); scanVars(n, null); } } /** * Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); declareVar(child); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(n.getFirstChild()); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: sourceName = (String) n.getProp(Node.SOURCENAME_PROP); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null;) { Node next = child.getNext(); scanVars(child, n); child = next; } } } /** * Interface for injectable duplicate handling. */ interface RedeclarationHandler { void onRedeclaration( Scope s, String name, Node n, CompilerInput input); } /** * The default handler for duplicate declarations. */ private class DefaultRedeclarationHandler implements RedeclarationHandler { public void on

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Redeclaration( Scope s, String name, Node n, CompilerInput input) { Node parent = n.getParent(); // Don't allow multiple variables to be declared at the top level scope if (scope.isGlobal()) { Scope.Var origVar = scope.getVar(name); Node origParent = origVar.getParentNode(); if (origParent.getType() == Token.CATCH && parent.getType() == Token.CATCH) { // Okay, both are 'catch(x)' variables. return; } boolean allowDupe = false; JSDocInfo info = n.getJSDocInfo(); if (info == null) { info = parent.getJSDocInfo(); } allowDupe = info != null && info.getSuppressions().contains("duplicate"); if (!allowDupe) { compiler.report( JSError.make(sourceName, n, VAR_MULTIPLY_DECLARED_ERROR, name, (origVar.input != null ? origVar.input.getName() : "??"))); } } else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) { // Disallow shadowing "arguments" as we can't handle with our current // scope modeling. compiler.report( JSError.make(sourceName, n, VAR_ARGUMENTS_SHADOWED_ERROR)); } } } /** * Declares a variable. * * @param n The node corresponding to the variable name. * @param declaredType The variable's type, according to JSDoc */ private void declareVar(Node n) { Preconditions.checkState(n.getType() == Token.NAME); CompilerInput input = compiler.getInput(sourceName); String name = n.getString(); if (scope.isDeclared(name, false) || (scope.isLocal() && name.equals(ARGUMENTS))) { redeclarationHandler.onRedeclaration( scope, name, n, input); } else { scope.declare(name, n, null, input); } } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2009 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.Sets; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSType; import com.google.javascript.rhino.jstype.ObjectType; import java.nio.charset.Charset; import java.util.Set; /** * A code generator that outputs type annotations for functions and * constructors. */ class TypedCodeGenerator extends CodeGenerator { TypedCodeGenerator(CodeConsumer consumer, Charset outputCharset) { super(consumer, outputCharset); } @Override void add(Node n, Context context) { Node parent = n.getParent(); if (parent != null && (parent.getType() == Token.BLOCK || parent.getType() == Token.SCRIPT)) { if (n.getType() == Token.FUNCTION) { add(getFunctionAnnotation(n)); } else if (n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN) { Node rhs = n.getFirstChild().getLastChild(); add(getTypeAnnotation(rhs)); } else if (n.getType() == Token.VAR && n.getFirstChild().getFirstChild() != null && n.getFirstChild().getFirstChild().getType() == Token.FUNCTION) { add(getFunctionAnnotation(n.getFirstChild().getFirstChild())); } } super.add(n, context); } private String getTypeAnnotation(Node node) { JSType type = node.getJSType(); if (type instanceof FunctionType) { return getFunctionAnnotation(node); } else if (type != null && !type.isUnknownType() && !type.isEmptyType() && !type.isVoidType() && !type.isFunctionPrototypeType()) { return "/** @type {" + node.getJSType() + "} */\n"; } else { return ""; } } /** * @param fnNode A node for a function for which to generate a type annotation */ private String getFunctionAnnotation(Node fnNode) { Preconditions.checkState(fn

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Node.getType() == Token.FUNCTION); StringBuilder sb = new StringBuilder("/**\n"); JSType type = fnNode.getJSType(); if (type == null || type.isUnknownType()) { return ""; } FunctionType funType = (FunctionType) fnNode.getJSType(); // We need to use the child nodes of the function as the nodes for the // parameters of the function type do not have the real parameter names. // FUNCTION // NAME // LP // NAME param1 // NAME param2 if (fnNode != null) { Node paramNode = NodeUtil.getFnParameters(fnNode).getFirstChild(); // Param types for (Node n : funType.getParameters()) { // Bail out if the paramNode is not there. if (paramNode == null) { break; } sb.append(" * @param {" + getParameterNodeJSDocType(n) + "} "); sb.append(paramNode.getString()); sb.append("\n"); paramNode = paramNode.getNext(); } } // Return type JSType retType = funType.getReturnType(); if (retType != null && !retType.isUnknownType() && !retType.isEmptyType()) { sb.append(" * @return {" + retType + "}\n"); } // Constructor/interface if (funType.isConstructor() || funType.isInterface()) { FunctionType superConstructor = funType.getSuperClassConstructor(); if (superConstructor != null) { ObjectType superInstance = funType.getSuperClassConstructor().getInstanceType(); if (!superInstance.toString().equals("Object")) { sb.append(" * @extends {" + superInstance + "}\n"); } } // Avoid duplicates, add implemented type to a set first Set<String> interfaces = Sets.newTreeSet(); for (ObjectType interfaze : funType.getImplementedInterfaces()) { interfaces.add(interfaze.toString()); } for (String interfaze : interfaces) { sb.append(" * @implements {" + interfaze + "}\n"); } if (funType.isConstructor()) { sb.append(" * @constructor\n"); } else if (funType.isInterface()) { sb.append(" * @interface\n"); } } if (fnNode != null && fnNode.getBooleanProp(Node.IS_DISPATCHER)) { sb.append(" * @javadispatch\n"); } sb.append(" */\n"); return sb.toString(); } /** * Creates a JSDoc-suitable String representation the type of a parameter. * * @param parameterNode The parameter node. */ private String getParameterNodeJSDocType(Node parameterNode) { JSType parameterType = parameterNode.getJSType(); String typeString; // Emit unknown types as '*' (AllType) since '?' (UnknownType) is not

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> final DiagnosticType DEPRECATED_CLASS = DiagnosticType.disabled( "JSC_DEPRECATED_CLASS", "Class {0} has been deprecated."); static final DiagnosticType DEPRECATED_CLASS_REASON = DiagnosticType.disabled( "JSC_DEPRECATED_CLASS_REASON", "Class {0} has been deprecated: {1}"); static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_GLOBAL_ACCESS", "Access to private variable {0} not allowed outside file {1}."); static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PRIVATE_PROPERTY_ACCESS", "Access to private property {0} of {1} not allowed here."); static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS = DiagnosticType.disabled( "JSC_BAD_PROTECTED_PROPERTY_ACCESS", "Access to protected property {0} of {1} not allowed here."); static final DiagnosticType PRIVATE_OVERRIDE = DiagnosticType.disabled( "JSC_PRIVATE_OVERRIDE", "Overriding private property of {0}."); static final DiagnosticType VISIBILITY_MISMATCH = DiagnosticType.disabled( "JSC_VISIBILITY_MISMATCH", "Overriding {0} property of {1} with {2} property."); private final AbstractCompiler compiler; private final TypeValidator validator; // State about the current traversal. private int deprecatedDepth = 0; private int methodDepth = 0; private JSType currentClass = null; CheckAccessControls(AbstractCompiler compiler) { this.compiler = compiler; this.validator = compiler.getTypeValidator(); } public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, this); } public void enterScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n, parent)) { deprecatedDepth++; } if (methodDepth == 0) { currentClass = getClassOfMethod(n, parent); } methodDepth++; } } public void exitScope(NodeTraversal t) { if (!t.inGlobalScope()) { Node n = t.getScopeRoot(); Node parent = n.getParent(); if (isDeprecatedFunction(n, parent)) { deprecatedDepth--; } methodDepth--; if (methodDepth == 0) { currentClass = null; } } } /** * Gets the type of the class that "owns" a method, or null if * we know that its un-owned. */ private JSType getClassOfMethod(Node n, Node parent) { if (parent.getType() == Token.ASSIGN) { Node lValue = parent.getFirstChild(); if (lValue.isQualifiedName()) { if (lValue.getType() == Token.GETPROP) {

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> // We have an assignment of the form "a.b = ...". JSType lValueType = lValue.getJSType(); if (lValueType != null && lValueType.isConstructor()) { // If a.b is a constructor, then everything in this function // belongs to the "a.b" type. return ((FunctionType) lValueType).getInstanceType(); } else { // If a.b is not a constructor, then treat this as a method // of whatever type is on "a". return normalizeClassType(lValue.getFirstChild().getJSType()); } } else { // We have an assignment of the form "a = ...", so pull the // type off the "a". return normalizeClassType(lValue.getJSType()); } } } else if (NodeUtil.isFunctionDeclaration(n) || parent.getType() == Token.NAME) { return normalizeClassType(n.getJSType()); } return null; } /** * Normalize the type of a constructor, its instance, and its prototype * all down to the same type (the instance type). */ private JSType normalizeClassType(JSType type) { if (type == null || type.isUnknownType()) { return type; } else if (type.isConstructor()) { return ((FunctionType) type).getInstanceType(); } else if (type.isFunctionPrototypeType()) { FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction(); if (owner.isConstructor()) { return owner.getInstanceType(); } } return type; } public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { return true; } public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.NAME: checkNameDeprecation(t, n, parent); checkNameVisibility(t, n, parent); break; case Token.GETPROP: checkPropertyDeprecation(t, n, parent); checkPropertyVisibility(t, n, parent); break; case Token.NEW: checkConstructorDeprecation(t, n, parent); break; } } /** * Checks the given NEW node to ensure that access restrictions are obeyed. */ private void checkConstructorDeprecation(NodeTraversal t, Node n, Node parent) { JSType type = n.getJSType(); if (type != null) { String deprecationInfo = getTypeDeprecationInfo(type); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_CLASS_REASON, type.toString(), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_CLASS, type.toString())); } } } } /** * Checks the given NAME node to ensure that access restrictions are obeyed.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> */ private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking definitions or constructors. if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.NEW) { return; } Scope.Var var = t.getScope().getVar(n.getString()); JSDocInfo docInfo = var == null ? null : var.getJSDocInfo(); if (docInfo != null && docInfo.isDeprecated() && shouldEmitDeprecationWarning(t, n, parent)) { if (docInfo.getDeprecationReason() != null) { compiler.report( t.makeError(n, DEPRECATED_NAME_REASON, n.getString(), docInfo.getDeprecationReason())); } else { compiler.report( t.makeError(n, DEPRECATED_NAME, n.getString())); } } } /** * Checks the given GETPROP node to ensure that access restrictions are * obeyed. */ private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) { // Don't bother checking constructors. if (parent.getType() == Token.NEW) { return; } ObjectType objectType = ObjectType.cast(dereference(n.getFirstChild().getJSType())); String propertyName = n.getLastChild().getString(); if (objectType != null) { String deprecationInfo = getPropertyDeprecationInfo(objectType, propertyName); if (deprecationInfo != null && shouldEmitDeprecationWarning(t, n, parent)) { if (!deprecationInfo.isEmpty()) { compiler.report( t.makeError(n, DEPRECATED_PROP_REASON, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true), deprecationInfo)); } else { compiler.report( t.makeError(n, DEPRECATED_PROP, propertyName, validator.getReadableJSTypeName(n.getFirstChild(), true))); } } } } /** * Determines whether the given name is visible in the current context. * @param t The current traversal. * @param name The name node. */ private void checkNameVisibility(NodeTraversal t, Node name, Node parent) { Var var = t.getScope().getVar(name.getString()); if (var != null) { JSDocInfo docInfo = var.getJSDocInfo(); if (docInfo != null) { // If a name is private, make sure that we're in the same file. Visibility visibility = docInfo.getVisibility(); if (visibility == Visibility.PRIVATE && !t.getInput().getName().equals(docInfo.getSourceName())) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } compiler.report( t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS, name.getString(), docInfo.getSourceName())); } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> } } /** * Determines whether the given property is visible in the current context. * @param t The current traversal. * @param getprop The getprop node. */ private void checkPropertyVisibility(NodeTraversal t, Node getprop, Node parent) { ObjectType objectType = ObjectType.cast(dereference(getprop.getFirstChild().getJSType())); String propertyName = getprop.getLastChild().getString(); if (objectType != null) { // Is this a normal property access, or are we trying to override // an existing property? boolean isOverride = t.inGlobalScope() && parent.getType() == Token.ASSIGN && parent.getFirstChild() == getprop; // Find the lowest property defined on a class with visibility // information. if (isOverride) { objectType = objectType.getImplicitPrototype(); } JSDocInfo docInfo = null; for (; objectType != null; objectType = objectType.getImplicitPrototype()) { docInfo = objectType.getOwnPropertyJSDocInfo(propertyName); if (docInfo != null && docInfo.getVisibility() != Visibility.INHERITED) { break; } } if (objectType == null) { // We couldn't find a visibility modifier; assume it's public. return; } boolean sameInput = t.getInput().getName().equals(docInfo.getSourceName()); Visibility visibility = docInfo.getVisibility(); JSType ownerType = normalizeClassType(objectType); if (isOverride) { // Check an ASSIGN statement that's trying to override a property // on a superclass. JSDocInfo overridingInfo = parent.getJSDocInfo(); Visibility overridingVisibility = overridingInfo == null ? Visibility.INHERITED : overridingInfo.getVisibility(); // Check that (a) the property *can* be overridden, and // (b) that the visibility of the override is the same as the // visibility of the original property. if (visibility == Visibility.PRIVATE && !sameInput) { compiler.report( t.makeError(getprop, PRIVATE_OVERRIDE, objectType.toString())); } else if (overridingVisibility != Visibility.INHERITED && overridingVisibility != visibility) { compiler.report( t.makeError(getprop, VISIBILITY_MISMATCH, visibility.name(), objectType.toString(), overridingVisibility.name())); } } else { if (sameInput) { // private access is always allowed in the same file. return; } else if (visibility == Visibility.PRIVATE && (currentClass == null || ownerType.differsFrom(currentClass))) { if (docInfo.isConstructor() && isValidPrivateConstructorAccess(parent)) { return; } // private access is not allowed outside the file from a different // enclosing class. compiler.report( t.makeError(getprop, BAD_PRIVATE_PROPERTY_ACCESS, propertyName, validator.getReadable

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>JSTypeName( getprop.getFirstChild(), true))); } else if (visibility == Visibility.PROTECTED) { // There are 3 types of legal accesses of a protected property: // 1) Accesses in the same file // 2) Overriding the property in a subclass // 3) Accessing the property from inside a subclass // The first two have already been checked for. if (currentClass == null || !currentClass.isSubtype(ownerType)) { compiler.report( t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS, propertyName, validator.getReadableJSTypeName( getprop.getFirstChild(), true))); } } } } } /** * Whether the given access of a private constructor is legal. * * For example, * new PrivateCtor_(); // not legal * PrivateCtor_.newInstance(); // legal * x instanceof PrivateCtor_ // legal * * This is a weird special case, because our visibility system is inherited * from Java, and JavaScript has no distinction between classes and * constructors like Java does. * * We may want to revisit this if we decide to make the restrictions tighter. */ private static boolean isValidPrivateConstructorAccess(Node parent) { return parent.getType() != Token.NEW; } /** * Determines whether a deprecation warning should be emitted. * @param t The current traversal. * @param n The node which we are checking. * @param parent The parent of the node which we are checking. */ private boolean shouldEmitDeprecationWarning( NodeTraversal t, Node n, Node parent) { // In the global scope, there are only two kinds of accesses that should // be flagged for warnings: // 1) Calls of deprecated functions and methods. // 2) Instantiations of deprecated classes. // For now, we just let everything else by. if (t.inGlobalScope()) { if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) || n.getType() == Token.NEW)) { return false; } } // We can always assign to a deprecated property, to keep it up to date. if (n.getType() == Token.GETPROP && n == parent.getFirstChild() && NodeUtil.isAssignmentOp(parent)) { return false; } return !canAccessDeprecatedTypes(t); } /** * Returns whether it's currently ok to access deprecated names and * properties. * * There are 3 exceptions when we're allowed to use a deprecated * type or property: * 1) When we're in a deprecated function. * 2) When we're in a deprecated class. * 3) When we're in a static method of a deprecated class. */ private boolean canAccessDeprecatedTypes(NodeTraversal t) { Node scopeRoot = t.getScopeRoot(); Node scopeRootParent = scopeRoot.getParent(); return // Case #1

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> boolean addOptionalParams(JSType ...types) { if (hasVarArgs()) { return false; } for (JSType type : types) { newParameter(registry.createOptionalType(type)).setOptionalArg(true); } return true; } /** * Add variable arguments to the end of the parameter list. * @return False if this is called after var args are added. */ public boolean addVarArgs(JSType type) { if (hasVarArgs()) { return false; } // There are two types of variable argument functions: // 1) Programmer-defined var args // 2) Native bottom types that can accept any argument. // For the first one, "undefined" is a valid value for all arguments. // For the second, we do not want to cast it up to undefined. if (!type.isEmptyType()) { type = registry.createOptionalType(type); } newParameter(type).setVarArgs(true); return true; } /** * Copies the parameter specification from the given node. */ public Node newParameterFromNode(Node n) { Node newParam = newParameter(n.getJSType()); newParam.setVarArgs(n.isVarArgs()); newParam.setOptionalArg(n.isOptionalArg()); return newParam; } // Add a parameter to the list with the given type. private Node newParameter(JSType type) { Node paramNode = Node.newString(Token.NAME, ""); paramNode.setJSType(type); root.addChildToBack(paramNode); return paramNode; } public Node build() { return root; } private boolean hasOptionalOrVarArgs() { Node lastChild = root.getLastChild(); return lastChild != null && (lastChild.isOptionalArg() || lastChild.isVarArgs()); } public boolean hasVarArgs() { Node lastChild = root.getLastChild(); return lastChild != null && lastChild.isVarArgs(); } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> && c <= 9) { long v = c; for (int i = 1; i != len; ++i) { c = str.charAt(i) - '0'; if (!(0 <= c && c <= 9)) { return -1; } v = 10 * v + c; } // Check for overflow if ((v >>> 32) == 0) { return v; } } } return -1; } static boolean isSpecialProperty(String s) { return s.equals("__proto__") || s.equals("__parent__"); } // ------------------ // Statements // ------------------ public static String getMessage0(String messageId) { return getMessage(messageId, null); } public static String getMessage1(String messageId, Object arg1) { Object[] arguments = {arg1}; return getMessage(messageId, arguments); } public static String getMessage2( String messageId, Object arg1, Object arg2) { Object[] arguments = {arg1, arg2}; return getMessage(messageId, arguments); } public static String getMessage3( String messageId, Object arg1, Object arg2, Object arg3) { Object[] arguments = {arg1, arg2, arg3}; return getMessage(messageId, arguments); } public static String getMessage4( String messageId, Object arg1, Object arg2, Object arg3, Object arg4) { Object[] arguments = {arg1, arg2, arg3, arg4}; return getMessage(messageId, arguments); } /* OPT there's a noticable delay for the first error! Maybe it'd * make sense to use a ListResourceBundle instead of a properties * file to avoid (synchronized) text parsing. */ public static String getMessage(String messageId, Object[] arguments) { final String defaultResource = "rhino_ast.java.com.google.javascript.rhino.Messages"; Context cx = Context.getCurrentContext(); Locale locale = cx != null ? cx.getLocale() : Locale.getDefault(); // ResourceBundle does cacheing. ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale); String formatString; try { formatString = rb.getString(messageId); } catch (java.util.MissingResourceException mre) { throw new RuntimeException ("no message resource found for message property "+ messageId); } /* * It's OK to format the string, even if 'arguments' is null; * we need to format it anyway, to make double ''s collapse to * single 's. */ // TODO: MessageFormat is not available on pJava MessageFormat formatter = new MessageFormat(formatString); return formatter.format(arguments); } public static EcmaError constructError(String error, String message) { int[] linep = new int[1]; String filename = Context.getSourcePositionFromStack(linep); return

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2007 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSTypeRegistry; import com.google.javascript.rhino.jstype.ObjectType; import java.util.Collection; import java.util.Collections; import java.util.List; /** * CodingConvention defines a set of hooks to customize the behavior of the * Compiler for a specific team/company. * */ public class DefaultCodingConvention implements CodingConvention { private static final long serialVersionUID = 1L; @Override public boolean isConstant(String variableName) { return false; } @Override public boolean isConstantKey(String variableName) { return false; } @Override public boolean isValidEnumKey(String key) { return key != null && key.length() > 0; } @Override public boolean isOptionalParameter(Node parameter) { // be as lax as possible, but this must be mutually exclusive from // var_args parameters. return !isVarArgsParameter(parameter); } @Override public boolean isVarArgsParameter(Node parameter) { // be as lax as possible return parameter.getParent().getLastChild() == parameter; } @Override public boolean isExported(String name, boolean local) { return local && name.startsWith("$super"); } @Override public boolean isExported(String name) { return isExported(name, false) || isExported(name, true); } @Override public boolean isPrivate(String name) { return false; } @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { return null; } @Override public boolean isSuperClassReference(String propertyName) { return false; } @Override public String extractClassNameIfProvide(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String extractClassNameIfRequire(Node node, Node parent) { String message = "only implemented in GoogleCodingConvention"; throw new UnsupportedOperationException(message); } @Override public String getExportPropertyFunction() { return null; }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> exact call flow that // clients are currently using. In that flow, they call // getProvides(), then remove the goog.provide calls from the // AST, and then call getProvides() again. // // This won't work for any other call flow, or any sort of incremental // compilation scheme. The API needs to be fixed so callers aren't // doing weird things like this, and then we should get rid of the // multiple-scan strategy. provides.addAll(finder.provides); requires.addAll(finder.requires); } else { // Otherwise, look at the source code. if (!generatedDependencyInfoFromSource) { // Note: it's ok to use getName() instead of // getPathRelativeToClosureBase() here because we're not using // this to generate deps files. (We're only using it for // symbol dependencies.) DependencyInfo info = (new JsFileParser(errorManager)).parseFile( getName(), getName(), getCode()); provides.addAll(info.getProvides()); requires.addAll(info.getRequires()); generatedDependencyInfoFromSource = true; } } } private static class DepsFinder { private final List<String> provides = Lists.newArrayList(); private final List<String> requires = Lists.newArrayList(); private final CodingConvention codingConvention = new ClosureCodingConvention(); void visitTree(Node n) { visitSubtree(n, null); } void visitSubtree(Node n, Node parent) { if (n.getType() == Token.CALL) { String require = codingConvention.extractClassNameIfRequire(n, parent); if (require != null) { requires.add(require); } String provide = codingConvention.extractClassNameIfProvide(n, parent); if (provide != null) { provides.add(provide); } return; } else if (parent != null && parent.getType() != Token.EXPR_RESULT && parent.getType() != Token.SCRIPT) { return; } for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { visitSubtree(child, n); } } } /** * Gets the source line for the indicated line number. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Does not include the newline at the end * of the file. Returns {@code null} if it does not exist, * or if there was an IO exception. */ public String getLine(int lineNumber) { return getSourceFile().getLine(lineNumber); } /** * Get a region around the indicated line number. The exact definition of a * region is implementation specific, but it must contain the line indicated * by the line number. A region must not start or end by a carriage return. * * @param lineNumber the line number, 1 being the first line of the file. * @return The line indicated. Returns

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> if (node instanceof Block) { return processBlock((Block) node); } else if (node instanceof Scope) { return processScope((Scope) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.BREAK: return processBreakStatement((BreakStatement) node); case Token.CALL: return processFunctionCall((FunctionCall) node); case Token.CASE: case Token.DEFAULT: return processSwitchCase((SwitchCase) node); case Token.CATCH: case Token.FINALLY: return processCatchClause((CatchClause) node); case Token.COLON: return processObjectProperty((ObjectProperty) node); case Token.CONTINUE: return processContinueStatement((ContinueStatement) node); case Token.DO: return processDoLoop((DoLoop) node); case Token.EMPTY: return processEmptyExpression((EmptyExpression) node); case Token.EXPR_RESULT: case Token.EXPR_VOID: if (node instanceof ExpressionStatement) { return processExpressionStatement((ExpressionStatement) node); } else if (node instanceof LabeledStatement) { return processLabeledStatement((LabeledStatement) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.DEBUGGER: case Token.FALSE: case Token.NULL: case Token.THIS: case Token.TRUE: return processKeywordLiteral((KeywordLiteral) node); case Token.FOR: if (node instanceof ForInLoop) { return processForInLoop((ForInLoop) node); } else if (node instanceof ForLoop) { return processForLoop((ForLoop) node); } else { throw new IllegalStateException("Unexpected node type. class: " + node.getClass() + " type: " + Token.typeToName(node.getType())); } case Token.FUNCTION: return processFunctionNode((FunctionNode) node); case Token.GETELEM: return processElementGet((ElementGet) node); case Token.GETPROP: return processPropertyGet((PropertyGet) node); case Token.HOOK: return processConditionalExpression((ConditionalExpression) node); case Token.IF: return processIfStatement((IfStatement) node); case Token.LABEL: return processLabel((Label) node); case Token.LP: return processParenthesizedExpression((ParenthesizedExpression) node); case Token.NAME: return processName((Name) node); case Token.NEW: return processNewExpression((NewExpression) node); case Token.NUMBER: return processNumberLiteral((NumberLiteral) node); case Token.OBJECTLIT: return processObjectLiteral((ObjectLiteral) node); case Token.REGEXP: return processRegExpLiteral((RegExpLiteral) node); case Token.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import com.google.javascript.rhino.jstype.TernaryValue; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * NodeUtil contains utilities that get properties from the Node object. * */ public final class NodeUtil { final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty"; // TODO(user): Eliminate this class and make all of the static methods // instance methods of com.google.javascript.rhino.Node. /** the set of builtin constructors that don't have side effects. */ private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS = new HashSet<String>(Arrays.asList( "Array", "Date", "Error", "Object", "RegExp", "XMLHttpRequest")); // Utility class; do not instantiate. private NodeUtil() {} /** * Gets the boolean value of a node that represents a expression. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. * Note: unlike getBooleanValue this function does not return UNKNOWN * for expressions with side-effects. */ static TernaryValue getExpressionBooleanValue(Node n) { switch (n.getType()) { case Token.ASSIGN: case Token.COMMA: // For ASSIGN and COMMA the value is the value of the RHS. return getExpressionBooleanValue(n.getLastChild()); case Token.NOT: TernaryValue value = getExpressionBooleanValue(n.getLastChild()); return value.not();

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> case Token.AND: { TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild()); TernaryValue rhs = getExpressionBooleanValue(n.getLastChild()); return lhs.and(rhs); } case Token.OR: { TernaryValue lhs = getExpressionBooleanValue(n.getFirstChild()); TernaryValue rhs = getExpressionBooleanValue(n.getLastChild()); return lhs.or(rhs); } case Token.HOOK: { TernaryValue trueValue = getExpressionBooleanValue( n.getFirstChild().getNext()); TernaryValue falseValue = getExpressionBooleanValue(n.getLastChild()); if (trueValue.equals(falseValue)) { return trueValue; } else { return TernaryValue.UNKNOWN; } } default: return getBooleanValue(n); } } /** * Gets the boolean value of a node that represents a literal. This method * effectively emulates the <code>Boolean()</code> JavaScript cast function. */ static TernaryValue getBooleanValue(Node n) { switch (n.getType()) { case Token.STRING: return TernaryValue.forBoolean(n.getString().length() > 0); case Token.NUMBER: return TernaryValue.forBoolean(n.getDouble() != 0); case Token.NULL: case Token.FALSE: case Token.VOID: return TernaryValue.FALSE; case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "NaN".equals(name)) { // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return TernaryValue.FALSE; } else if ("Infinity".equals(name)) { return TernaryValue.TRUE; } break; case Token.TRUE: case Token.ARRAYLIT: case Token.OBJECTLIT: case Token.REGEXP: return TernaryValue.TRUE; } return TernaryValue.UNKNOWN; } /** * Gets the value of a node as a String, or null if it cannot be converted. * When it returns a non-null String, this method effectively emulates the * <code>String()</code> JavaScript cast function. */ static String getStringValue(Node n) { // TODO(user): Convert constant array, object, and regex literals as well. switch (n.getType()) { case Token.STRING: return n.getString(); case Token.NAME: String name = n.getString(); if ("undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name)) { return name; } break; case Token.NUMBER: double value = n.getDouble(); long longValue = (long) value; // Return "1" instead of "1.0" if (longValue == value) { return Long.toString(longValue); } else { return Double.toString

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>(n.getDouble()); } case Token.FALSE: case Token.TRUE: case Token.NULL: return Node.tokenToName(n.getType()); case Token.VOID: return "undefined"; } return null; } /** * Gets the value of a node as a Number, or null if it cannot be converted. * When it returns a non-null Double, this method effectively emulates the * <code>Number()</code> JavaScript cast function. */ static Double getNumberValue(Node n) { switch (n.getType()) { case Token.TRUE: return 1.0; case Token.FALSE: case Token.NULL: return 0.0; case Token.NUMBER: return n.getDouble(); case Token.VOID: return Double.NaN; case Token.NAME: String name = n.getString(); if (name.equals("undefined")) { return Double.NaN; } if (name.equals("NaN")) { return Double.NaN; } if (name.equals("Infinity")) { return Double.POSITIVE_INFINITY; } return null; } return null; } /** * Gets the function's name. This method recognizes five forms: * <ul> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * In two last cases with named function expressions, the second name is * returned (the variable of qualified name). * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getFunctionName(Node n) { Node parent = n.getParent(); String name = n.getFirstChild().getString(); switch (parent.getType()) { case Token.NAME: // var name = function() ... // var name2 = function name1() ... return parent.getString(); case Token.ASSIGN: // qualified.name = function() ... // qualified.name2 = function name1() ... return parent.getFirstChild().getQualifiedName(); default: // function name() ... return name != null && name.length() != 0 ? name : null; } } /** * Gets the function's name. This method recognizes the forms: * <ul> * <li>{@code &#123;'name': function() ...&#125;}</li> * <li>{@code &#123;name: function() ...&#125;}</li> * <li>{@code function name() ...}</li> * <li>{@code var name = function() ...}</li> * <li

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>>{@code qualified.name = function() ...}</li> * <li>{@code var name2 = function name1() ...}</li> * <li>{@code qualified.name2 = function name1() ...}</li> * </ul> * * @param n a node whose type is {@link Token#FUNCTION} * @return the function's name, or {@code null} if it has no name */ static String getNearestFunctionName(Node n) { String name = getFunctionName(n); if (name != null) { return name; } // Check for the form { 'x' : function() { } } Node parent = n.getParent(); switch (parent.getType()) { case Token.STRING: // Return the name of the literal's key. return getStringValue(parent); } return null; } /** * Returns true if this is an immutable value. */ static boolean isImmutableValue(Node n) { switch (n.getType()) { case Token.STRING: case Token.NUMBER: case Token.NULL: case Token.TRUE: case Token.FALSE: return true; case Token.VOID: case Token.NEG: return isImmutableValue(n.getFirstChild()); case Token.NAME: String name = n.getString(); // We assume here that programs don't change the value of the keyword // undefined to something other than the value undefined. return "undefined".equals(name) || "Infinity".equals(name) || "NaN".equals(name); } return false; } /** * Returns true if this is a literal value. We define a literal value * as any node that evaluates to the same thing regardless of when or * where it is evaluated. So /xyz/ and [3, 5] are literals, but * the name a is not. * * Function literals do not meet this definition, because they * lexically capture variables. For example, if you have * <code> * function() { return a; } * </code> * If it is evaluated in a different scope, then it * captures a different variable. Even if the function did not read * any captured vairables directly, it would still fail this definition, * because it affects the lifecycle of variables in the enclosing scope. * * However, a function literal with respect to a particular scope is * a literal. * * @param includeFunctions If true, all function expressions will be * treated as literals. */ static boolean isLiteralValue(Node n, boolean includeFunctions) { switch (n.getType()) { case Token.ARRAYLIT: case Token.REGEXP: // Return true only if all children are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child, includeFunctions)) { return false; } } return true; case Token.OBJECTLIT: // Return true

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> only if all values are const. for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (!isLiteralValue(child.getFirstChild(), includeFunctions)) { return false; } } return true; case Token.FUNCTION: return includeFunctions && !NodeUtil.isFunctionDeclaration(n); default: return isImmutableValue(n); } } /** * Determines whether the given value may be assigned to a define. * * @param val The value being assigned. * @param defines The list of names of existing defines. */ static boolean isValidDefineValue(Node val, Set<String> defines) { switch (val.getType()) { case Token.STRING: case Token.NUMBER: case Token.TRUE: case Token.FALSE: return true; // Binary operators are only valid if both children are valid. case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case Token.BITXOR: case Token.DIV: case Token.EQ: case Token.GE: case Token.GT: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.URSH: return isValidDefineValue(val.getFirstChild(), defines) && isValidDefineValue(val.getLastChild(), defines); // Uniary operators are valid if the child is valid. case Token.NOT: case Token.NEG: case Token.POS: return isValidDefineValue(val.getFirstChild(), defines); // Names are valid if and only if they are defines themselves. case Token.NAME: case Token.GETPROP: if (val.isQualifiedName()) { return defines.contains(val.getQualifiedName()); } } return false; } /** * Returns whether this a BLOCK node with no children. * * @param block The node. */ static boolean isEmptyBlock(Node block) { if (block.getType() != Token.BLOCK) { return false; } for (Node n = block.getFirstChild(); n != null; n = n.getNext()) { if (n.getType() != Token.EMPTY) { return false; } } return true; } static boolean isSimpleOperator(Node n) { return isSimpleOperatorType(n.getType()); } /** * A "simple" operator is one whose children are expressions, * has no direct side-effects (unlike '+='), and has no * conditional aspects (unlike '||'). */ static boolean isSimpleOperatorType(int type) { switch (type) { case Token.ADD: case Token.BITAND: case Token.BITNOT: case Token.BITOR: case

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> Token.BITXOR: case Token.COMMA: case Token.DIV: case Token.EQ: case Token.GE: case Token.GETELEM: case Token.GETPROP: case Token.GT: case Token.INSTANCEOF: case Token.LE: case Token.LSH: case Token.LT: case Token.MOD: case Token.MUL: case Token.NE: case Token.NOT: case Token.RSH: case Token.SHEQ: case Token.SHNE: case Token.SUB: case Token.TYPEOF: case Token.VOID: case Token.POS: case Token.NEG: case Token.URSH: return true; default: return false; } } /** * Creates an EXPR_RESULT. * * @param child The expression itself. * @return Newly created EXPR node with the child as subexpression. */ public static Node newExpr(Node child) { Node expr = new Node(Token.EXPR_RESULT, child) .copyInformationFrom(child); return expr; } /** * Returns true if the node may create new mutable state, or change existing * state. * * @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a> */ static boolean mayEffectMutableState(Node n) { return mayEffectMutableState(n, null); } static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, true, compiler); } /** * Returns true if the node which may have side effects when executed. */ static boolean mayHaveSideEffects(Node n) { return mayHaveSideEffects(n, null); } static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) { return checkForStateChangeHelper(n, false, compiler); } /** * Returns true if some node in n's subtree changes application state. * If {@code checkForNewObjects} is true, we assume that newly created * mutable objects (like object literals) change state. Otherwise, we assume * that they have no side effects. */ private static boolean checkForStateChangeHelper( Node n, boolean checkForNewObjects, AbstractCompiler compiler) { // Rather than id which ops may have side effects, id the ones // that we know to be safe switch (n.getType()) { // other side-effect free statements and expressions case Token.AND: case Token.BLOCK: case Token.EXPR_RESULT: case Token.HOOK: case Token.IF: case Token.IN: case Token.LP: case Token.NUMBER: case Token.OR: case Token.THIS: case Token.TRUE: case Token.FALSE: case Token.NULL: case Token.STRING: case Token.SWITCH: case Token.TRY: case Token.EMPTY: break; //

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> Throws are by definition side effects case Token.THROW: return true; case Token.OBJECTLIT: if (checkForNewObjects) { return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper( c.getFirstChild(), checkForNewObjects, compiler)) { return true; } } return false; case Token.ARRAYLIT: case Token.REGEXP: if (checkForNewObjects) { return true; } break; case Token.VAR: // empty var statement (no declaration) case Token.NAME: // variable by itself if (n.getFirstChild() != null) { return true; } break; case Token.FUNCTION: // Function expressions don't have side-effects, but function // declarations change the namespace. Either way, we don't need to // check the children, since they aren't executed at declaration time. return checkForNewObjects || !isFunctionExpression(n); case Token.NEW: if (checkForNewObjects) { return true; } if (!constructorCallHasSideEffects(n)) { // loop below will see if the constructor parameters have // side-effects break; } return true; case Token.CALL: // calls to functions that have no side effects have the no // side effect property set. if (!functionCallHasSideEffects(n, compiler)) { // loop below will see if the function parameters have // side-effects break; } return true; default: if (isSimpleOperatorType(n.getType())) { break; } if (isAssignmentOp(n)) { Node assignTarget = n.getFirstChild(); if (isName(assignTarget)) { return true; } // Assignments will have side effects if // a) The RHS has side effects, or // b) The LHS has side effects, or // c) A name on the LHS will exist beyond the life of this statement. if (checkForStateChangeHelper( n.getFirstChild(), checkForNewObjects, compiler) || checkForStateChangeHelper( n.getLastChild(), checkForNewObjects, compiler)) { return true; } if (isGet(assignTarget)) { // If the object being assigned to is a local object, don't // consider this a side-effect as it can't be referenced // elsewhere. Don't do this recursively as the property might // be an alias of another object, unlike a literal below. Node current = assignTarget.getFirstChild(); if (evaluatesToLocalValue(current)) { return false; } // A literal value as defined by "isLiteralValue" is guaranteed // not to be an alias, or any components which are aliases of // other objects. // If the root object is a literal don't consider this a // side-effect. while (isGet(current)) { current = current

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>.getFirstChild(); } return !isLiteralValue(current, true); } else { // TODO(johnlenz): remove this code and make this an exception. This // is here only for legacy reasons, the AST is not valid but // preserve existing behavior. return !isLiteralValue(assignTarget, true); } } return true; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) { return true; } } return false; } /** * Do calls to this constructor have side effects? * * @param callNode - construtor call node */ static boolean constructorCallHasSideEffects(Node callNode) { return constructorCallHasSideEffects(callNode, null); } static boolean constructorCallHasSideEffects( Node callNode, AbstractCompiler compiler) { if (callNode.getType() != Token.NEW) { throw new IllegalStateException( "Expected NEW node, got " + Token.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } Node nameNode = callNode.getFirstChild(); if (nameNode.getType() == Token.NAME && CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) { return false; } return true; } // A list of built-in object creation or primitive type cast functions that // can also be called as constructors but lack side-effects. // TODO(johnlenz): consider adding an extern annotation for this. private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS = ImmutableSet.of( "Object", "Array", "String", "Number", "Boolean", "RegExp", "Error"); private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS = ImmutableSet.of("toString", "valueOf"); private static final Set<String> REGEXP_METHODS = ImmutableSet.of("test", "exec"); private static final Set<String> STRING_REGEXP_METHODS = ImmutableSet.of("match", "replace", "search", "split"); /** * Returns true if calls to this function have side effects. * * @param callNode - function call node */ static boolean functionCallHasSideEffects( Node callNode) { return functionCallHasSideEffects(callNode, null); } /** * Returns true if calls to this function have side effects. * * @param callNode The call node to inspected. * @param compiler A compiler object to provide program state changing * context information. Can be null. */ static boolean functionCallHasSideEffects( Node callNode, @Nullable AbstractCompiler compiler) { if (callNode.getType() != Token.CALL) { throw new IllegalStateException( "Expected CALL node, got " + Token

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>.name(callNode.getType())); } if (callNode.isNoSideEffectsCall()) { return false; } Node nameNode = callNode.getFirstChild(); // Built-in functions with no side effects. if (nameNode.getType() == Token.NAME) { String name = nameNode.getString(); if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) { return false; } } else if (nameNode.getType() == Token.GETPROP) { if (callNode.hasOneChild() && OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains( nameNode.getLastChild().getString())) { return false; } if (callNode.isOnlyModifiesThisCall() && evaluatesToLocalValue(nameNode.getFirstChild())) { return false; } // Functions in the "Math" namespace have no side effects. if (nameNode.getFirstChild().getType() == Token.NAME) { String namespaceName = nameNode.getFirstChild().getString(); if (namespaceName.equals("Math")) { return false; } } if (compiler != null && !compiler.hasRegExpGlobalReferences()) { if (nameNode.getFirstChild().getType() == Token.REGEXP && REGEXP_METHODS.contains(nameNode.getLastChild().getString())) { return false; } else if (nameNode.getFirstChild().getType() == Token.STRING && STRING_REGEXP_METHODS.contains( nameNode.getLastChild().getString())) { Node param = nameNode.getNext(); if (param != null && (param.getType() == Token.STRING || param.getType() == Token.REGEXP)) return false; } } } return true; } /** * @return Whether the call has a local result. */ static boolean callHasLocalResult(Node n) { Preconditions.checkState(n.getType() == Token.CALL); return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0; } /** * Returns true if the current node's type implies side effects. * * This is a non-recursive version of the may have side effects * check; used to check wherever the current node's type is one of * the reason's why a subtree has side effects. */ static boolean nodeTypeMayHaveSideEffects(Node n) { return nodeTypeMayHaveSideEffects(n, null); } static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) { if (isAssignmentOp(n)) { return true; } switch(n.getType()) { case Token.DELPROP: case Token.DEC: case Token.INC: case Token.THROW: return true; case Token.CALL: return NodeUtil.functionCallHasSideEffects(n, compiler); case Token.NEW: return NodeUtil.constructorCallHasSideEffects(n, compiler

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>); case Token.NAME: // A variable definition. return n.hasChildren(); default: return false; } } /** * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n) { Set<String> emptySet = Collections.emptySet(); return canBeSideEffected(n, emptySet); } /** * @param knownConstants A set of names known to be constant value at * node 'n' (such as locals that are last written before n can execute). * @return Whether the tree can be affected by side-effects or * has side-effects. */ static boolean canBeSideEffected(Node n, Set<String> knownConstants) { switch (n.getType()) { case Token.CALL: case Token.NEW: // Function calls or constructor can reference changed values. // TODO(johnlenz): Add some mechanism for determining that functions // are unaffected by side effects. return true; case Token.NAME: // Non-constant names values may have been changed. return !isConstantName(n) && !knownConstants.contains(n.getString()); // Properties on constant NAMEs can still be side-effected. case Token.GETPROP: case Token.GETELEM: return true; case Token.FUNCTION: // Function expression are not changed by side-effects, // and function declarations are not part of expressions. Preconditions.checkState(isFunctionExpression(n)); return false; } for (Node c = n.getFirstChild(); c != null; c = c.getNext()) { if (canBeSideEffected(c, knownConstants)) { return true; } } return false; } /* * 0 comma , * 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |= * 2 conditional ?: * 3 logical-or || * 4 logical-and && * 5 bitwise-or | * 6 bitwise-xor ^ * 7 bitwise-and & * 8 equality == != * 9 relational < <= > >= * 10 bitwise shift << >> >>> * 11 addition/subtraction + - * 12 multiply/divide * / % * 13 negation/increment ! ~ - ++ -- * 14 call, member () [] . */ static int precedence(int type) { switch (type) { case Token.COMMA: return 0; case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>_MOD: case Token.ASSIGN: return 1; case Token.HOOK: return 2; // ?: operator case Token.OR: return 3; case Token.AND: return 4; case Token.BITOR: return 5; case Token.BITXOR: return 6; case Token.BITAND: return 7; case Token.EQ: case Token.NE: case Token.SHEQ: case Token.SHNE: return 8; case Token.LT: case Token.GT: case Token.LE: case Token.GE: case Token.INSTANCEOF: case Token.IN: return 9; case Token.LSH: case Token.RSH: case Token.URSH: return 10; case Token.SUB: case Token.ADD: return 11; case Token.MUL: case Token.MOD: case Token.DIV: return 12; case Token.INC: case Token.DEC: case Token.NEW: case Token.DELPROP: case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: return 13; case Token.ARRAYLIT: case Token.CALL: case Token.EMPTY: case Token.FALSE: case Token.FUNCTION: case Token.GETELEM: case Token.GETPROP: case Token.GET_REF: case Token.IF: case Token.LP: case Token.NAME: case Token.NULL: case Token.NUMBER: case Token.OBJECTLIT: case Token.REGEXP: case Token.RETURN: case Token.STRING: case Token.THIS: case Token.TRUE: return 15; default: throw new Error("Unknown precedence for " + Node.tokenToName(type) + " (type " + type + ")"); } } /** * Returns true if the operator is associative. * e.g. (a * b) * c = a * (b * c) * Note: "+" is not associative because it is also the concatenation * for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2 */ static boolean isAssociative(int type) { switch (type) { case Token.MUL: case Token.AND: case Token.OR: case Token.BITOR: case Token.BITAND: return true; default: return false; } } /** * Returns true if the operator is commutative. * e.g. (a * b) * c = c * (b * a) * Note 1: "+" is not commutative because it is also the concatenation * for strings. e.g. "a" + (1 + 2

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>) is not "a" + 1 + 2 * Note 2: only operations on literals and pure functions are commutative. */ static boolean isCommutative(int type) { switch (type) { case Token.MUL: case Token.BITOR: case Token.BITAND: return true; default: return false; } } static boolean isAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: return true; } return false; } static int getOpFromAssignmentOp(Node n) { switch (n.getType()){ case Token.ASSIGN_BITOR: return Token.BITOR; case Token.ASSIGN_BITXOR: return Token.BITXOR; case Token.ASSIGN_BITAND: return Token.BITAND; case Token.ASSIGN_LSH: return Token.LSH; case Token.ASSIGN_RSH: return Token.RSH; case Token.ASSIGN_URSH: return Token.URSH; case Token.ASSIGN_ADD: return Token.ADD; case Token.ASSIGN_SUB: return Token.SUB; case Token.ASSIGN_MUL: return Token.MUL; case Token.ASSIGN_DIV: return Token.DIV; case Token.ASSIGN_MOD: return Token.MOD; } throw new IllegalArgumentException("Not an assiment op"); } static boolean isExpressionNode(Node n) { return n.getType() == Token.EXPR_RESULT; } /** * Determines if the given node contains a function statement or function * expression. */ static boolean containsFunction(Node n) { return containsType(n, Token.FUNCTION); } /** * Returns true if the shallow scope contains references to 'this' keyword */ static boolean referencesThis(Node n) { return containsType(n, Token.THIS, new MatchNotFunction()); } /** * Is this a GETPROP or GETELEM node? */ static boolean isGet(Node n) { return n.getType() == Token.GETPROP || n.getType() == Token.GETELEM; } /** * Is this a GETPROP node? */ static boolean isGetProp(Node n) { return n.getType() == Token.GETPROP; } /** * Is this a NAME node? */ static boolean isName(Node n) { return n.getType() == Token.NAME; } /** * Is this a NEW node? */ static boolean isNew(Node n

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>) { return n.getType() == Token.NEW; } /** * Is this a VAR node? */ static boolean isVar(Node n) { return n.getType() == Token.VAR; } /** * Is this node the name of a variable being declared? * * @param n The node * @return True if {@code n} is NAME and {@code parent} is VAR */ static boolean isVarDeclaration(Node n) { // There is no need to verify that parent != null because a NAME node // always has a parent in a valid parse tree. return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR; } /** * For an assignment or variable declaration get the assigned value. * @return The value node representing the new value. */ static Node getAssignedValue(Node n) { Preconditions.checkState(isName(n)); Node parent = n.getParent(); if (isVar(parent)) { return n.getFirstChild(); } else if (isAssign(parent) && parent.getFirstChild() == n) { return n.getNext(); } else { return null; } } /** * Is this a STRING node? */ static boolean isString(Node n) { return n.getType() == Token.STRING; } /** * Is this node an assignment expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is ASSIGN */ static boolean isExprAssign(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.ASSIGN; } /** * Is this an ASSIGN node? */ static boolean isAssign(Node n) { return n.getType() == Token.ASSIGN; } /** * Is this node a call expression statement? * * @param n The node * @return True if {@code n} is EXPR_RESULT and {@code n}'s * first child is CALL */ static boolean isExprCall(Node n) { return n.getType() == Token.EXPR_RESULT && n.getFirstChild().getType() == Token.CALL; } /** * @return Whether the node represents a FOR-IN loop. */ static boolean isForIn(Node n) { return n.getType() == Token.FOR && n.getChildCount() == 3; } /** * Determines whether the given node is a FOR, DO, or WHILE node. */ static boolean isLoopStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: return true; default: return false; } } /** * @param n The node to inspect. * @return If the node, is a FOR, WHILE,

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> or DO, it returns the node for * the code BLOCK, null otherwise. */ static Node getLoopCodeBlock(Node n) { switch (n.getType()) { case Token.FOR: case Token.WHILE: return n.getLastChild(); case Token.DO: return n.getFirstChild(); default: return null; } } /** * @return Whether the specified node has a loop parent that * is within the current scope. */ static boolean isWithinLoop(Node n) { for (Node parent : n.getAncestors()) { if (NodeUtil.isLoopStructure(parent)) { return true; } if (NodeUtil.isFunction(parent)) { break; } } return false; } /** * Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node. */ static boolean isControlStructure(Node n) { switch (n.getType()) { case Token.FOR: case Token.DO: case Token.WHILE: case Token.WITH: case Token.IF: case Token.LABEL: case Token.TRY: case Token.CATCH: case Token.SWITCH: case Token.CASE: case Token.DEFAULT: return true; default: return false; } } /** * Determines whether the given node is code node for FOR, DO, * WHILE, WITH, or IF node. */ static boolean isControlStructureCodeBlock(Node parent, Node n) { switch (parent.getType()) { case Token.FOR: case Token.WHILE: case Token.LABEL: case Token.WITH: return parent.getLastChild() == n; case Token.DO: return parent.getFirstChild() == n; case Token.IF: return parent.getFirstChild() != n; case Token.TRY: return parent.getFirstChild() == n || parent.getLastChild() == n; case Token.CATCH: return parent.getLastChild() == n; case Token.SWITCH: case Token.CASE: return parent.getFirstChild() != n; case Token.DEFAULT: return true; default: Preconditions.checkState(isControlStructure(parent)); return false; } } /** * Gets the condition of an ON_TRUE / ON_FALSE CFG edge. * @param n a node with an outgoing conditional CFG edge * @return the condition node or null if the condition is not obviously a node */ static Node getConditionExpression(Node n) { switch (n.getType()) { case Token.IF: case Token.WHILE: return n.getFirstChild(); case Token.DO: return n.getLastChild(); case Token.FOR: switch (n.getChildCount()) { case 3: return null; case 4: return n.getFirstChild().getNext(); } throw new IllegalArgumentException("malformed 'for' statement " + n);

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> case Token.CASE: return null; } throw new IllegalArgumentException(n + " does not have a condition."); } /** * @return Whether the node is of a type that contain other statements. */ static boolean isStatementBlock(Node n) { return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK; } /** * @return Whether the node is used as a statement. */ static boolean isStatement(Node n) { Node parent = n.getParent(); // It is not possible to determine definitely if a node is a statement // or not if it is not part of the AST. A FUNCTION node can be // either part of an expression or a statement. Preconditions.checkState(parent != null); switch (parent.getType()) { case Token.SCRIPT: case Token.BLOCK: case Token.LABEL: return true; default: return false; } } /** Whether the node is part of a switch statement. */ static boolean isSwitchCase(Node n) { return n.getType() == Token.CASE || n.getType() == Token.DEFAULT; } /** * @return Whether the name is a reference to a variable, function or * function parameter (not a label or a empty function expression name). */ static boolean isReferenceName(Node n) { return isName(n) && !n.getString().isEmpty(); } /** @return Whether the node is a label name. */ static boolean isLabelName(Node n) { return (n != null && n.getType() == Token.LABEL_NAME); } /** Whether the child node is the FINALLY block of a try. */ static boolean isTryFinallyNode(Node parent, Node child) { return parent.getType() == Token.TRY && parent.getChildCount() == 3 && child == parent.getLastChild(); } /** Safely remove children while maintaining a valid node structure. */ static void removeChild(Node parent, Node node) { // Node parent = node.getParent(); if (isStatementBlock(parent) || isSwitchCase(node) || isTryFinallyNode(parent, node)) { // A statement in a block can simply be removed. parent.removeChild(node); } else if (parent.getType() == Token.VAR) { if (parent.hasMoreThanOneChild()) { parent.removeChild(node); } else { // Remove the node from the parent, so it can be reused. parent.removeChild(node); // This would leave an empty VAR, remove the VAR itself. removeChild(parent.getParent(), parent); } } else if (node.getType() == Token.BLOCK) { // Simply empty the block. This maintains source location and // "synthetic"-ness. node.detachChildren(); } else if (parent.getType() == Token.LABEL && node == parent.getLastChild()) { // Remove the node from the parent, so it can be reused.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> parent.removeChild(node); // A LABEL without children can not be referred to, remove it. removeChild(parent.getParent(), parent); } else if (parent.getType() == Token.FOR && parent.getChildCount() == 4) { // Only Token.FOR can have an Token.EMPTY other control structure // need something for the condition. Others need to be replaced // or the structure removed. parent.replaceChild(node, new Node(Token.EMPTY)); } else { throw new IllegalStateException("Invalid attempt to remove node: " + node.toString() + " of "+ parent.toString()); } } /** * Merge a block with its parent block. * @return Whether the block was removed. */ static boolean tryMergeBlock(Node block) { Preconditions.checkState(block.getType() == Token.BLOCK); Node parent = block.getParent(); // Try to remove the block if its parent is a block/script or if its // parent is label and it has exactly one child. if (isStatementBlock(parent)) { Node previous = block; while (block.hasChildren()) { Node child = block.removeFirstChild(); parent.addChildAfter(child, previous); previous = child; } parent.removeChild(block); return true; } else { return false; } } /** * Is this a CALL node? */ static boolean isCall(Node n) { return n.getType() == Token.CALL; } /** * @param node A node * @return Whether the call is a NEW or CALL node. */ static boolean isCallOrNew(Node node) { return NodeUtil.isCall(node) || NodeUtil.isNew(node); } /** * Is this a FUNCTION node? */ static boolean isFunction(Node n) { return n.getType() == Token.FUNCTION; } /** * Return a BLOCK node for the given FUNCTION node. */ static Node getFunctionBody(Node fn) { Preconditions.checkArgument(isFunction(fn)); return fn.getLastChild(); } /** * Is this a THIS node? */ static boolean isThis(Node node) { return node.getType() == Token.THIS; } /** * Is this node or any of its children a CALL? */ static boolean containsCall(Node n) { return containsType(n, Token.CALL); } /** * Is this node a function declaration? A function declaration is a function * that has a name that is added to the current scope (i.e. a function that * is not part of a expression; see {@link #isFunctionExpression}). */ static boolean isFunctionDeclaration(Node n) { return n.getType() == Token.FUNCTION && isStatement(n); } /** * Is this node a hoisted function declaration? A function declaration in the * scope root is hoisted to the top of the scope. * See {@link #isFunctionDeclaration

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>}). */ static boolean isHoistedFunctionDeclaration(Node n) { return isFunctionDeclaration(n) && (n.getParent().getType() == Token.SCRIPT || n.getParent().getParent().getType() == Token.FUNCTION); } /** * Is a FUNCTION node an function expression? An function expression is one * that has either no name or a name that is not added to the current scope. * * <p>Some examples of function expressions: * <pre> * (function () {}) * (function f() {})() * [ function f() {} ] * var f = function f() {}; * for (function f() {};;) {} * </pre> * * <p>Some examples of functions that are <em>not</em> expressions: * <pre> * function f() {} * if (x); else function f() {} * for (;;) { function f() {} } * </pre> * * @param n A node * @return Whether n is an function used within an expression. */ static boolean isFunctionExpression(Node n) { return n.getType() == Token.FUNCTION && !isStatement(n); } /** * Determines if a node is a function expression that has an empty body. * * @param node a node * @return whether the given node is a function expression that is empty */ static boolean isEmptyFunctionExpression(Node node) { return isFunctionExpression(node) && isEmptyBlock(node.getLastChild()); } /** * Determines if a function takes a variable number of arguments by * looking for references to the "arguments" var_args object. */ static boolean isVarArgsFunction(Node function) { Preconditions.checkArgument(isFunction(function)); return isNameReferenced( function.getLastChild(), "arguments", new MatchNotFunction()); } /** * @return Whether node is a call to methodName. * a.f(...) * a['f'](...) */ static boolean isObjectCallMethod(Node callNode, String methodName) { if (callNode.getType() == Token.CALL) { Node functionIndentifyingExpression = callNode.getFirstChild(); if (isGet(functionIndentifyingExpression)) { Node last = functionIndentifyingExpression.getLastChild(); if (last != null && last.getType() == Token.STRING) { String propName = last.getString(); return (propName.equals(methodName)); } } } return false; } /** * @return Whether the callNode represents an expression in the form of: * x.call(...) * x['call'](...) */ static boolean isFunctionObjectCall(Node callNode) { return isObjectCallMethod(callNode, "call"); } /** * @return Whether the callNode represents an expression in the form of: * x.apply(...) * x['apply'](...) */ static boolean isFunctionObjectApply(Node callNode) {

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>VAR, Node.newString(Token.NAME, nameNode.getString()) .copyInformationFrom(nameNode)) .copyInformationFrom(nameNode); copyNameAnnotations(nameNode, var.getFirstChild()); parent.addChildToFront(var); } } /** * Copy any annotations that follow a named value. * @param source * @param destination */ static void copyNameAnnotations(Node source, Node destination) { if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) { destination.putBooleanProp(Node.IS_CONSTANT_NAME, true); } } /** * Gets a Node at the top of the current scope where we can add new var * declarations as children. */ private static Node getAddingRoot(Node n) { Node addingRoot = null; Node ancestor = n; while (null != (ancestor = ancestor.getParent())) { int type = ancestor.getType(); if (type == Token.SCRIPT) { addingRoot = ancestor; break; } else if (type == Token.FUNCTION) { addingRoot = ancestor.getLastChild(); break; } } // make sure that the adding root looks ok Preconditions.checkState(addingRoot.getType() == Token.BLOCK || addingRoot.getType() == Token.SCRIPT); Preconditions.checkState(addingRoot.getFirstChild() == null || addingRoot.getFirstChild().getType() != Token.SCRIPT); return addingRoot; } /** Creates function name(params_0, ..., params_n) { body }. */ public static Node newFunctionNode(String name, List<Node> params, Node body, int lineno, int charno) { Node parameterParen = new Node(Token.LP, lineno, charno); for (Node param : params) { parameterParen.addChildToBack(param); } Node function = new Node(Token.FUNCTION, lineno, charno); function.addChildrenToBack( Node.newString(Token.NAME, name, lineno, charno)); function.addChildToBack(parameterParen); function.addChildToBack(body); return function; } /** * Creates a node representing a qualified name. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @param lineno The source line offset. * @param charno The source character offset from start of the line. * @return A NAME or GETPROP node */ public static Node newQualifiedNameNode( CodingConvention convention, String name, int lineno, int charno) { int endPos = name.indexOf('.'); if (endPos == -1) { return newName(convention, name, lineno, charno); } Node node = newName( convention, name.substring(0, endPos), lineno, charno); int startPos; do { startPos = endPos + 1; endPos = name.indexOf('.', startPos); String part = (endPos

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> == -1 ? name.substring(startPos) : name.substring(startPos, endPos)); Node propNode = Node.newString(Token.STRING, part, lineno, charno); if (convention.isConstantKey(part)) { propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } node = new Node(Token.GETPROP, node, propNode, lineno, charno); } while (endPos != -1); return node; } /** * Creates a node representing a qualified name, copying over the source * location information from the basis node and assigning the given original * name to the node. * * @param name A qualified name (e.g. "foo" or "foo.bar.baz") * @param basisNode The node that represents the name as currently found in * the AST. * @param originalName The original name of the item being represented by the * NAME node. Used for debugging information. * * @return A NAME or GETPROP node */ static Node newQualifiedNameNode( CodingConvention convention, String name, Node basisNode, String originalName) { Node node = newQualifiedNameNode(convention, name, -1, -1); setDebugInformation(node, basisNode, originalName); return node; } /** * Gets the root node of a qualified name. Must be either NAME or THIS. */ static Node getRootOfQualifiedName(Node qName) { for (Node current = qName; true; current = current.getFirstChild()) { int type = current.getType(); if (type == Token.NAME || type == Token.THIS) { return current; } Preconditions.checkState(type == Token.GETPROP); } } /** * Sets the debug information (source file info and orignal name) * on the given node. * * @param node The node on which to set the debug information. * @param basisNode The basis node from which to copy the source file info. * @param originalName The original name of the node. */ static void setDebugInformation(Node node, Node basisNode, String originalName) { node.copyInformationFromForTree(basisNode); node.putProp(Node.ORIGINALNAME_PROP, originalName); } private static Node newName( CodingConvention convention, String name, int lineno, int charno) { Node nameNode = Node.newString(Token.NAME, name, lineno, charno); if (convention.isConstant(name)) { nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true); } return nameNode; } /** * Creates a new node representing an *existing* name, copying over the source * location information from the basis node. * * @param name The name for the new NAME node. * @param basisNode The node that represents the name as currently found in * the AST. *

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>newLinkedHashMap(); public void visit(Node n) { if (n.getType() == Token.NAME) { Node parent = n.getParent(); if (parent != null && parent.getType() == Token.VAR) { String name = n.getString(); if (!vars.containsKey(name)) { vars.put(name, n); } } } } } /** * Retrieves vars declared in the current node tree, excluding descent scopes. */ public static Collection<Node> getVarsDeclaredInBranch(Node root) { VarCollector collector = new VarCollector(); visitPreOrder( root, collector, new MatchNotFunction()); return collector.vars.values(); } /** * @return {@code true} if the node an assignment to a prototype property of * some constructor. */ static boolean isPrototypePropertyDeclaration(Node n) { if (!isExprAssign(n)) { return false; } return isPrototypeProperty(n.getFirstChild().getFirstChild()); } static boolean isPrototypeProperty(Node n) { String lhsString = n.getQualifiedName(); if (lhsString == null) { return false; } int prototypeIdx = lhsString.indexOf(".prototype."); return prototypeIdx != -1; } /** * @return The class name part of a qualified prototype name. */ static Node getPrototypeClassName(Node qName) { Node cur = qName; while (isGetProp(cur)) { if (cur.getLastChild().getString().equals("prototype")) { return cur.getFirstChild(); } else { cur = cur.getFirstChild(); } } return null; } /** * @return The string property name part of a qualified prototype name. */ static String getPrototypePropertyName(Node qName) { String qNameStr = qName.getQualifiedName(); int prototypeIdx = qNameStr.lastIndexOf(".prototype."); int memberIndex = prototypeIdx + ".prototype".length() + 1; return qNameStr.substring(memberIndex); } /** * Create a node for an empty result expression: * "void 0" */ static Node newUndefinedNode(Node srcReferenceNode) { // TODO(johnlenz): Why this instead of the more common "undefined"? Node node = new Node(Token.VOID, Node.newNumber(0)); if (srcReferenceNode != null) { node.copyInformationFromForTree(srcReferenceNode); } return node; } /** * Create a VAR node containing the given name and initial value expression. */ static Node newVarNode(String name, Node value) { Node nodeName = Node.newString(Token.NAME, name); if (value != null) { Preconditions.checkState(value.getNext() == null); nodeName.addChildToBack(value); nodeName.copyInformationFrom(value); } Node var = new Node(Token.VAR, nodeName) .copyInformation

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>From(nodeName); return var; } /** * A predicate for matching name nodes with the specified node. */ private static class MatchNameNode implements Predicate<Node>{ final String name; MatchNameNode(String name){ this.name = name; } public boolean apply(Node n) { return n.getType() == Token.NAME && n.getString().equals(name); } } /** * A predicate for matching nodes with the specified type. */ static class MatchNodeType implements Predicate<Node>{ final int type; MatchNodeType(int type){ this.type = type; } public boolean apply(Node n) { return n.getType() == type; } } /** * A predicate for matching var or function declarations. */ static class MatchDeclaration implements Predicate<Node> { public boolean apply(Node n) { return isFunctionDeclaration(n) || n.getType() == Token.VAR; } } /** * A predicate for matching anything except function nodes. */ static class MatchNotFunction implements Predicate<Node>{ public boolean apply(Node n) { return !isFunction(n); } } /** * A predicate for matching statements without exiting the current scope. */ static class MatchShallowStatement implements Predicate<Node>{ public boolean apply(Node n) { Node parent = n.getParent(); return n.getType() == Token.BLOCK || (!isFunction(n) && (parent == null || isControlStructure(parent) || isStatementBlock(parent))); } } /** * Finds the number of times a type is referenced within the node tree. */ static int getNodeTypeReferenceCount( Node node, int type, Predicate<Node> traverseChildrenPred) { return getCount(node, new MatchNodeType(type), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name, Predicate<Node> traverseChildrenPred) { return has(node, new MatchNameNode(name), traverseChildrenPred); } /** * Whether a simple name is referenced within the node tree. */ static boolean isNameReferenced(Node node, String name) { return isNameReferenced(node, name, Predicates.<Node>alwaysTrue()); } /** * Finds the number of times a simple name is referenced within the node tree. */ static int getNameReferenceCount(Node node, String name) { return getCount( node, new MatchNameNode(name), Predicates.<Node>alwaysTrue()); } /** * @return Whether the predicate is true for the node or any of its children. */ static boolean has(Node node, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) { if (pred.apply(node)) { return true; } if (!traverseChildrenPred.apply(node)) { return false; } for (Node c = node.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> NAME, LP -> ARG1, ARG2, ... ] Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION); return fnNode.getFirstChild().getNext(); } /** * Returns true if a name node represents a constant variable. * * <p>Determining whether a variable is constant has three steps: * <ol> * <li>In CodingConventionAnnotator, any name that matches the * {@link CodingConvention#isConstant(String)} is annotated with an * IS_CONSTANT_NAME property. * <li>The normalize pass renames any variable with the IS_CONSTANT_NAME * annotation and that is initialized to a constant value with * a variable name inlucding $$constant. * <li>Return true here if the variable includes $$constant in its name. * </ol> * * @param node A NAME or STRING node * @return True if the variable is constant */ static boolean isConstantName(Node node) { return node.getBooleanProp(Node.IS_CONSTANT_NAME); } /** Whether the given name is constant by coding convention. */ static boolean isConstantByConvention( CodingConvention convention, Node node, Node parent) { String name = node.getString(); if (parent.getType() == Token.GETPROP && node == parent.getLastChild()) { return convention.isConstantKey(name); } else if (isObjectLitKey(node, parent)) { return convention.isConstantKey(name); } else { return convention.isConstant(name); } } /** * @param nameNode A name node * @return The JSDocInfo for the name node */ static JSDocInfo getInfoForNameNode(Node nameNode) { JSDocInfo info = null; Node parent = null; if (nameNode != null) { info = nameNode.getJSDocInfo(); parent = nameNode.getParent(); } if (info == null && parent != null && ((parent.getType() == Token.VAR && parent.hasOneChild()) || parent.getType() == Token.FUNCTION)) { info = parent.getJSDocInfo(); } return info; } /** * Get the JSDocInfo for a function. */ static JSDocInfo getFunctionInfo(Node n) { Preconditions.checkState(n.getType() == Token.FUNCTION); JSDocInfo fnInfo = n.getJSDocInfo(); if (fnInfo == null && NodeUtil.isFunctionExpression(n)) { // Look for the info on other nodes. Node parent = n.getParent(); if (parent.getType() == Token.ASSIGN) { // on ASSIGNs fnInfo = parent.getJSDocInfo(); } else if (parent.getType() == Token.NAME) { // on var NAME = function() { ... }; fnInfo = parent.getParent().getJSDocInfo(); } } return fnInfo;

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> } /** * @param n The node. * @return The source name property on the node or its ancestors. */ static String getSourceName(Node n) { String sourceName = null; while (sourceName == null && n != null) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); n = n.getParent(); } return sourceName; } /** * A new CALL node with the "FREE_CALL" set based on call target. */ static Node newCallNode(Node callTarget, Node... parameters) { boolean isFreeCall = isName(callTarget); Node call = new Node(Token.CALL, callTarget); call.putBooleanProp(Node.FREE_CALL, isFreeCall); for (Node parameter : parameters) { call.addChildToBack(parameter); } return call; } /** * @return Whether the node is known to be a value that is not referenced * elsewhere. */ static boolean evaluatesToLocalValue(Node value) { return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse()); } /** * @param locals A predicate to apply to unknown local values. * @return Whether the node is known to be a value that is not a reference * outside the expression scope. */ static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) { switch (value.getType()) { case Token.ASSIGN: // A result that is aliased by a non-local name, is the effectively the // same as returning a non-local name, but this doesn't matter if the // value is immutable. return NodeUtil.isImmutableValue(value.getLastChild()) || (locals.apply(value) && evaluatesToLocalValue(value.getLastChild(), locals)); case Token.COMMA: return evaluatesToLocalValue(value.getLastChild(), locals); case Token.AND: case Token.OR: return evaluatesToLocalValue(value.getFirstChild(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.HOOK: return evaluatesToLocalValue(value.getFirstChild().getNext(), locals) && evaluatesToLocalValue(value.getLastChild(), locals); case Token.INC: case Token.DEC: if (value.getBooleanProp(Node.INCRDECR_PROP)) { return evaluatesToLocalValue(value.getFirstChild(), locals); } else { return true; } case Token.THIS: return locals.apply(value); case Token.NAME: return isImmutableValue(value) || locals.apply(value); case Token.GETELEM: case Token.GETPROP: // There is no information about the locality of object properties. return locals.apply(value); case Token.CALL: return callHasLocalResult(value) || isToStringMethodCall(value) || locals.apply(value); case Token.NEW: return true; case Token.FUNCTION:

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> case Token.REGEXP: case Token.ARRAYLIT: case Token.OBJECTLIT: // Literals objects with non-literal children are allowed. return true; case Token.IN: // TODO(johnlenz): should IN operator be included in #isSimpleOperator? return true; default: // Other op force a local value: // x = '' + g (x is now an local string) // x -= g (x is now an local number) if (isAssignmentOp(value) || isSimpleOperator(value) || isImmutableValue(value)) { return true; } throw new IllegalStateException( "Unexpected expression node" + value + "\n parent:" + value.getParent()); } } /** * Given the first sibling, this returns the nth * sibling or null if no such sibling exists. * This is like "getChildAtIndex" but returns null for non-existent indexes. */ private static Node getNthSibling(Node first, int index) { Node sibling = first; while (index != 0 && sibling != null) { sibling = sibling.getNext(); index--; } return sibling; } /** * Given the function, this returns the nth * argument or null if no such parameter exists. */ static Node getArgumentForFunction(Node function, int index) { Preconditions.checkState(isFunction(function)); return getNthSibling( function.getFirstChild().getNext().getFirstChild(), index); } /** * Given the new or call, this returns the nth * argument of the call or null if no such argument exists. */ static Node getArgumentForCallOrNew(Node call, int index) { Preconditions.checkState(isCallOrNew(call)); return getNthSibling( call.getFirstChild().getNext(), index); } private static boolean isToStringMethodCall(Node call) { Node getNode = call.getFirstChild(); if (isGet(getNode)) { Node propNode = getNode.getLastChild(); return isString(propNode) && "toString".equals(propNode.getString()); } return false; } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2004 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.javascript.jscomp.NodeUtil.MatchNotFunction; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.TokenStream; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; /** * CodeGenerator generates codes from a parse tree, sending it to the specified * CodeConsumer. * */ class CodeGenerator { private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private final CodeConsumer cc; private final CharsetEncoder outputCharsetEncoder; CodeGenerator( CodeConsumer consumer, Charset outputCharset) { cc = consumer; if (outputCharset == null || outputCharset == Charsets.US_ASCII) { // If we want our default (pretending to be UTF-8, but escaping anything // outside of straight ASCII), then don't use the encoder, but // just special-case the code. This keeps the normal path through // the code identical to how it's been for years. this.outputCharsetEncoder = null; } else { this.outputCharsetEncoder = outputCharset.newEncoder(); } } CodeGenerator(CodeConsumer consumer) { this(consumer, null); } void add(String str) { cc.add(str); } private void addIdentifier(String identifier) { cc.addIdentifier(identifierEscape(identifier)); } void add(Node n) { add(n, Context.OTHER); } void add(Node n, Context context) { if (!cc.continueProcessing()) { return; } int type = n.getType(); String opstr = NodeUtil.opToStr(type); int childCount = n.getChildCount(); Node first = n.getFirstChild(); Node last = n.getLastChild(); // Handle all binary operators if (opstr != null

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> && first != last) { Preconditions.checkState( childCount == 2, "Bad binary operator \"%s\": expected 2 arguments but got %s", opstr, childCount); int p = NodeUtil.precedence(type); addLeftExpr(first, p, context); cc.addOp(opstr, true); // For right-hand-side of operations, only pass context if it's // the IN_FOR_INIT_CLAUSE one. Context rhsContext = getContextForNoInOperator(context); // Handle associativity. // e.g. if the parse tree is a * (b * c), // we can simply generate a * b * c. if (last.getType() == type && NodeUtil.isAssociative(type)) { addExpr(last, p, rhsContext); } else if (NodeUtil.isAssignmentOp(n) && NodeUtil.isAssignmentOp(last)) { // Assignments are the only right-associative binary operators addExpr(last, p, rhsContext); } else { addExpr(last, p + 1, rhsContext); } return; } cc.startSourceMapping(n); switch (type) { case Token.TRY: { Preconditions.checkState(first.getNext().getType() == Token.BLOCK && !first.getNext().hasMoreThanOneChild()); Preconditions.checkState(childCount >= 2 && childCount <= 3); add("try"); add(first, Context.PRESERVE_BLOCK); // second child contains the catch block, or nothing if there // isn't a catch block Node catchblock = first.getNext().getFirstChild(); if (catchblock != null) { add(catchblock); } if (childCount == 3) { add("finally"); add(last, Context.PRESERVE_BLOCK); } break; } case Token.CATCH: Preconditions.checkState(childCount == 2); add("catch("); add(first); add(")"); add(last, Context.PRESERVE_BLOCK); break; case Token.THROW: Preconditions.checkState(childCount == 1); add("throw"); add(first); // Must have a ';' after a throw statement, otherwise safari can't // parse this. cc.endStatement(true); break; case Token.RETURN: add("return"); if (childCount == 1) { add(first); } else { Preconditions.checkState(childCount == 0); } cc.endStatement(); break; case Token.VAR: if (first != null) { add("var "); addList(first, false, getContextForNoInOperator(context)); } break; case Token.LABEL_NAME: Preconditions.checkState(!n.getString().isEmpty()); addIdentifier(n.getString()); break; case Token.NAME: if (first == null

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> || first.getType() == Token.EMPTY) { addIdentifier(n.getString()); } else { Preconditions.checkState(childCount == 1); addIdentifier(n.getString()); cc.addOp("=", true); if (first.getType() == Token.COMMA) { addExpr(first, NodeUtil.precedence(Token.ASSIGN)); } else { // Add expression, consider nearby code at lowest level of // precedence. addExpr(first, 0, getContextForNoInOperator(context)); } } break; case Token.ARRAYLIT: add("["); addList(first, (int[]) n.getProp(Node.SKIP_INDEXES_PROP)); add("]"); break; case Token.LP: add("("); addList(first); add(")"); break; case Token.COMMA: Preconditions.checkState(childCount == 2); addList(first, false, context); break; case Token.NUMBER: Preconditions.checkState( childCount == ((n.getParent() != null && n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)); cc.addNumber(n.getDouble()); break; case Token.TYPEOF: case Token.VOID: case Token.NOT: case Token.BITNOT: case Token.POS: case Token.NEG: { // All of these unary operators are right-associative Preconditions.checkState(childCount == 1); cc.addOp(NodeUtil.opToStrNoFail(type), false); addExpr(first, NodeUtil.precedence(type)); break; } case Token.HOOK: { Preconditions.checkState(childCount == 3); int p = NodeUtil.precedence(type); addLeftExpr(first, p + 1, context); cc.addOp("?", true); addExpr(first.getNext(), 1); cc.addOp(":", true); addExpr(last, 1); break; } case Token.REGEXP: if (first.getType() != Token.STRING || last.getType() != Token.STRING) { throw new Error("Expected children to be strings"); } String regexp = regexpEscape(first.getString(), outputCharsetEncoder); // I only use one .add because whitespace matters if (childCount == 2) { add(regexp + last.getString()); } else { Preconditions.checkState(childCount == 1); add(regexp); } break; case Token.GET_REF: add(first); break; case Token.REF_SPECIAL: Preconditions.checkState(childCount == 1); add(first); add("."); add((String) n.getProp(Node.NAME_PROP)); break; case Token.FUNCTION: if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> Preconditions.checkState(childCount == 3); boolean funcNeedsParens = (context == Context.START_OF_EXPR); if (funcNeedsParens) { add("("); } add("function"); add(first); add(first.getNext()); add(last, Context.PRESERVE_BLOCK); cc.endFunction(context == Context.STATEMENT); if (funcNeedsParens) { add(")"); } break; case Token.GET: case Token.SET: Preconditions.checkState(n.getParent().getType() == Token.OBJECTLIT); Preconditions.checkState(childCount == 1); Preconditions.checkState(first.getType() == Token.FUNCTION); // Get methods are unnamed Preconditions.checkState(first.getFirstChild().getString().isEmpty()); if (type == Token.GET) { // Get methods have no parameters. Preconditions.checkState(!first.getChildAtIndex(1).hasChildren()); add("get "); } else { // Set methods have one parameter. Preconditions.checkState(first.getChildAtIndex(1).hasOneChild()); add("set "); } // The name is on the GET or SET node. String name = n.getString(); Node fn = first; Node parameters = fn.getChildAtIndex(1); Node body = fn.getLastChild(); // Add the property name. if (TokenStream.isJSIdentifier(name) && // do not encode literally any non-literal characters that were // unicode escaped. NodeUtil.isLatin(name)) { add(name); } else { add(jsString(n.getString(), outputCharsetEncoder)); } add(parameters); add(body, Context.PRESERVE_BLOCK); break; case Token.SCRIPT: case Token.BLOCK: { if (n.getClass() != Node.class) { throw new Error("Unexpected Node subclass."); } boolean preserveBlock = context == Context.PRESERVE_BLOCK; if (preserveBlock) { cc.beginBlock(); } boolean preferLineBreaks = type == Token.SCRIPT || (type == Token.BLOCK && !preserveBlock && n.getParent() != null && n.getParent().getType() == Token.SCRIPT); for (Node c = first; c != null; c = c.getNext()) { add(c, Context.STATEMENT); // VAR doesn't include ';' since it gets used in expressions if (c.getType() == Token.VAR) { cc.endStatement(); } if (c.getType() == Token.FUNCTION) { cc.maybeLineBreak(); } // Prefer to break lines in between top-level statements // because top level statements are more homogeneous. if (preferLineBreaks) { cc.notePreferredLineBreak(); } } if (preserveBlock) { cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> } break; } case Token.FOR: if (childCount == 4) { add("for("); if (first.getType() == Token.VAR) { add(first, Context.IN_FOR_INIT_CLAUSE); } else { addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE); } add(";"); add(first.getNext()); add(";"); add(first.getNext().getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { Preconditions.checkState(childCount == 3); add("for("); add(first); add("in"); add(first.getNext()); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } break; case Token.DO: Preconditions.checkState(childCount == 2); add("do"); addNonEmptyStatement(first, Context.OTHER, false); add("while("); add(last); add(")"); cc.endStatement(); break; case Token.WHILE: Preconditions.checkState(childCount == 2); add("while("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.EMPTY: Preconditions.checkState(childCount == 0); break; case Token.GETPROP: { Preconditions.checkState( childCount == 2, "Bad GETPROP: expected 2 children, but got %s", childCount); Preconditions.checkState( last.getType() == Token.STRING, "Bad GETPROP: RHS should be STRING"); boolean needsParens = (first.getType() == Token.NUMBER); if (needsParens) { add("("); } addLeftExpr(first, NodeUtil.precedence(type), context); if (needsParens) { add(")"); } add("."); addIdentifier(last.getString()); break; } case Token.GETELEM: Preconditions.checkState( childCount == 2, "Bad GETELEM: expected 2 children but got %s", childCount); addLeftExpr(first, NodeUtil.precedence(type), context); add("["); add(first.getNext()); add("]"); break; case Token.WITH: Preconditions.checkState(childCount == 2); add("with("); add(first); add(")"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); break; case Token.INC: case Token.DEC: { Preconditions.checkState(childCount == 1); String o = type == Token.INC ? "++" : "--"; int postProp = n.getIntProp(Node.INCRDECR_PROP); // A non-

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>zero post-prop value indicates a post inc/dec, default of zero // is a pre-inc/dec. if (postProp != 0) { addLeftExpr(first, NodeUtil.precedence(type), context); cc.addOp(o, false); } else { cc.addOp(o, false); add(first); } break; } case Token.CALL: // We have two special cases here: // 1) If the left hand side of the call is a direct reference to eval, // then it must have a DIRECT_EVAL annotation. If it does not, then // that means it was originally an indirect call to eval, and that // indirectness must be preserved. // 2) If the left hand side of the call is a property reference, // then the call must not a FREE_CALL annotation. If it does, then // that means it was originally an call without an explicit this and // that must be preserved. if (isIndirectEval(first) || n.getBooleanProp(Node.FREE_CALL) && NodeUtil.isGet(first)) { add("(0,"); addExpr(first, NodeUtil.precedence(Token.COMMA)); add(")"); } else { addLeftExpr(first, NodeUtil.precedence(type), context); } add("("); addList(first.getNext()); add(")"); break; case Token.IF: boolean hasElse = childCount == 3; boolean ambiguousElseClause = context == Context.BEFORE_DANGLING_ELSE && !hasElse; if (ambiguousElseClause) { cc.beginBlock(); } add("if("); add(first); add(")"); if (hasElse) { addNonEmptyStatement( first.getNext(), Context.BEFORE_DANGLING_ELSE, false); add("else"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), false); } else { addNonEmptyStatement(first.getNext(), Context.OTHER, false); Preconditions.checkState(childCount == 2); } if (ambiguousElseClause) { cc.endBlock(); } break; case Token.NULL: case Token.THIS: case Token.FALSE: case Token.TRUE: Preconditions.checkState(childCount == 0); add(Node.tokenToName(type)); break; case Token.CONTINUE: Preconditions.checkState(childCount <= 1); add("continue"); if (childCount == 1) { if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first); } cc.endStatement(); break; case Token.DEBUGGER: Preconditions.checkState(childCount == 0); add("debugger"); cc.endStatement(); break; case Token.BREAK

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>: Preconditions.checkState(childCount <= 1); add("break"); if (childCount == 1) { if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(" "); add(first); } cc.endStatement(); break; case Token.EXPR_VOID: throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT."); case Token.EXPR_RESULT: Preconditions.checkState(childCount == 1); add(first, Context.START_OF_EXPR); cc.endStatement(); break; case Token.NEW: add("new "); int precedence = NodeUtil.precedence(type); // If the first child contains a CALL, then claim higher precedence // to force parentheses. Otherwise, when parsed, NEW will bind to the // first viable parentheses (don't traverse into functions). if (NodeUtil.containsType(first, Token.CALL, new MatchNotFunction())) { precedence = NodeUtil.precedence(first.getType()) + 1; } addExpr(first, precedence); // '()' is optional when no arguments are present Node next = first.getNext(); if (next != null) { add("("); addList(next); add(")"); } break; case Token.STRING: if (childCount != ((n.getParent() != null && n.getParent().getType() == Token.OBJECTLIT) ? 1 : 0)) { throw new IllegalStateException( "Unexpected String children: " + n.getParent().toStringTree()); } add(jsString(n.getString(), outputCharsetEncoder)); break; case Token.DELPROP: Preconditions.checkState(childCount == 1); add("delete "); add(first); break; case Token.OBJECTLIT: { boolean needsParens = (context == Context.START_OF_EXPR); if (needsParens) { add("("); } add("{"); for (Node c = first; c != null; c = c.getNext()) { if (c != first) { cc.listSeparator(); } if (c.getType() == Token.GET || c.getType() == Token.SET) { add(c); } else { // Object literal property names don't have to be quoted if they are // not JavaScript keywords if (c.getType() == Token.STRING && !TokenStream.isKeyword(c.getString()) && TokenStream.isJSIdentifier(c.getString()) && // do not encode literally any non-literal characters that were // unicode escaped. NodeUtil.isLatin(c.getString())) { add(c.getString()); } else { addExpr(c, 1); } add(":"); addExpr(c.getFirstChild(), 1); } } add("}"); if (needsParens)

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> { add(")"); } break; } case Token.SWITCH: add("switch("); add(first); add(")"); cc.beginBlock(); addAllSiblings(first.getNext()); cc.endBlock(context == Context.STATEMENT); break; case Token.CASE: Preconditions.checkState(childCount == 2); add("case "); add(first); addCaseBody(last); break; case Token.DEFAULT: Preconditions.checkState(childCount == 1); add("default"); addCaseBody(first); break; case Token.LABEL: Preconditions.checkState(childCount == 2); if (first.getType() != Token.LABEL_NAME) { throw new Error("Unexpected token type. Should be LABEL_NAME."); } add(first); add(":"); addNonEmptyStatement( last, getContextForNonEmptyExpression(context), true); break; // This node is auto generated in anonymous functions and should just get // ignored for our purposes. case Token.SETNAME: break; default: throw new Error("Unknown type " + type + "\n" + n.toStringTree()); } cc.endSourceMapping(n); } /** * @return Whether the name is an indirect eval. */ private boolean isIndirectEval(Node n) { return n.getType() == Token.NAME && "eval".equals(n.getString()) && !n.getBooleanProp(Node.DIRECT_EVAL); } /** * Adds a block or expression, substituting a VOID with an empty statement. * This is used for "for (...);" and "if (...);" type statements. * * @param n The node to print. * @param context The context to determine how the node should be printed. */ private void addNonEmptyStatement( Node n, Context context, boolean allowNonBlockChild) { Node nodeToProcess = n; if (!allowNonBlockChild && n.getType() != Token.BLOCK) { throw new Error("Missing BLOCK child."); } // Strip unneeded blocks, that is blocks with <2 children unless // the CodePrinter specifically wants to keep them. if (n.getType() == Token.BLOCK) { int count = getNonEmptyChildCount(n, 2); if (count == 0) { if (cc.shouldPreserveExtraBlocks()) { cc.beginBlock(); cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); } else { cc.endStatement(true); } return; } if (count == 1) { // Hack around a couple of browser bugs: // Safari needs a block around function declarations. // IE6/7 needs a block around DOs. Node firstAndOnlyChild = getFirstNonEmptyChild(n); boolean alwaysWrapInBlock = cc.shouldPreserveExtraBlocks(); if (alwaysWrapInBlock || isOne

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>ExactlyFunctionOrDo(firstAndOnlyChild)) { cc.beginBlock(); add(firstAndOnlyChild, Context.STATEMENT); cc.maybeLineBreak(); cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT)); return; } else { // Continue with the only child. nodeToProcess = firstAndOnlyChild; } } if (count > 1) { context = Context.PRESERVE_BLOCK; } } if (nodeToProcess.getType() == Token.EMPTY) { cc.endStatement(true); } else { add(nodeToProcess, context); // VAR doesn't include ';' since it gets used in expressions - so any // VAR in a statement context needs a call to endStatement() here. if (nodeToProcess.getType() == Token.VAR) { cc.endStatement(); } } } /** * @return Whether the Node is a DO or FUNCTION (with or without * labels). */ private boolean isOneExactlyFunctionOrDo(Node n) { if (n.getType() == Token.LABEL) { Node labeledStatement = n.getLastChild(); if (labeledStatement.getType() != Token.BLOCK) { return isOneExactlyFunctionOrDo(labeledStatement); } else { // For labels with block children, we need to ensure that a // labeled FUNCTION or DO isn't generated when extraneous BLOCKs // are skipped. if (getNonEmptyChildCount(n, 2) == 1) { return isOneExactlyFunctionOrDo(getFirstNonEmptyChild(n)); } else { // Either a empty statement or an block with more than one child, // way it isn't a FUNCTION or DO. return false; } } } else { return (n.getType() == Token.FUNCTION || n.getType() == Token.DO); } } /** * Adds a node at the left-hand side of an expression. Unlike * {@link #addExpr(Node,int)}, this preserves information about the context. * * The left side of an expression is special because in the JavaScript * grammar, certain tokens may be parsed differently when they are at * the beginning of a statement. For example, "{}" is parsed as a block, * but "{'x': 'y'}" is parsed as an object literal. */ void addLeftExpr(Node n, int minPrecedence, Context context) { addExpr(n, minPrecedence, context); } void addExpr(Node n, int minPrecedence) { addExpr(n, minPrecedence, Context.OTHER); } private void addExpr(Node n, int minPrecedence, Context context) { if ((NodeUtil.precedence(n.getType()) < minPrecedence) || ((context == Context.IN_FOR_INIT_CLAUSE) && (n.getType() == Token.IN))){ add("("); add(n

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> if (info == null) { return this; } else { return inferParameterTypes(info); } } // arguments Node oldParameterType = null; if (parametersNode != null) { oldParameterType = parametersNode.getFirstChild(); } FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry); boolean warnedAboutArgList = false; Set<String> allJsDocParams = (info == null) ? Sets.<String>newHashSet() : Sets.newHashSet(info.getParameterNames()); boolean foundTemplateType = false; for (Node arg : argsParent.children()) { String argumentName = arg.getString(); allJsDocParams.remove(argumentName); // type from JSDocInfo JSType parameterType = null; boolean isOptionalParam = isOptionalParameter(arg, info); boolean isVarArgs = isVarArgsParameter(arg, info); if (info != null && info.hasParameterType(argumentName)) { parameterType = info.getParameterType(argumentName).evaluate(scope, typeRegistry); } else if (oldParameterType != null && oldParameterType.getJSType() != null) { parameterType = oldParameterType.getJSType(); isOptionalParam = oldParameterType.isOptionalArg(); isVarArgs = oldParameterType.isVarArgs(); } else { parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE); } if (templateTypeName != null && parameterType.restrictByNotNullOrUndefined().isTemplateType()) { if (foundTemplateType) { reportError(TEMPLATE_TYPE_DUPLICATED, fnName); } foundTemplateType = true; } warnedAboutArgList |= addParameter( builder, parameterType, warnedAboutArgList, isOptionalParam, isVarArgs); if (oldParameterType != null) { oldParameterType = oldParameterType.getNext(); } } if (templateTypeName != null && !foundTemplateType) { reportError(TEMPLATE_TYPE_EXPECTED, fnName); } for (String inexistentName : allJsDocParams) { reportWarning(INEXISTANT_PARAM, inexistentName, fnName); } parametersNode = builder.build(); return this; } /** * @return Whether the given param is an optional param. */ private boolean isOptionalParameter( Node param, @Nullable JSDocInfo info) { if (codingConvention.isOptionalParameter(param)) { return true; } String paramName = param.getString(); return info != null && info.hasParameterType(paramName) && info.getParameterType(paramName).isOptionalArg(); } /** * Determine whether this is a var args parameter. * @return Whether the given param is a var args param. */ private boolean isVarArgsParameter( Node param, @Nullable JSDocInfo info) { if (codingConvention.isVarArgsParameter(param)) { return true; } String paramName = param.getString

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> */ private void checkDescendantNames(Name name, boolean nameIsDefined) { if (name.props != null) { for (Name prop : name.props) { // if the ancestor of a property is not defined, then we should emit // warnings for all references to the property. boolean propIsDefined = false; if (nameIsDefined) { // if the ancestor of a property is defined, then let's check that // the property is also explicitly defined if it needs to be. propIsDefined = (!propertyMustBeInitializedByFullName(prop) || prop.globalSets + prop.localSets > 0); } validateName(prop, propIsDefined); checkDescendantNames(prop, propIsDefined); } } } private void validateName(Name name, boolean isDefined) { // If the name is not defined, emit warnings for each reference. While // we're looking through each reference, check all the module dependencies. Ref declaration = name.declaration; if (!isDefined) { if (declaration != null) { reportRefToUndefinedName(name, declaration); } } if (name.refs != null) { JSModuleGraph moduleGraph = compiler.getModuleGraph(); for (Ref ref : name.refs) { if (!isDefined) { reportRefToUndefinedName(name, ref); } else { if (declaration != null && ref.module != declaration.module && !moduleGraph.dependsOn(ref.module, declaration.module)) { reportBadModuleReference(name, ref); } } } } } private void reportBadModuleReference(Name name, Ref ref) { compiler.report( JSError.make(ref.sourceName, ref.node, STRICT_MODULE_DEP_QNAME, ref.module.getName(), name.declaration.module.getName(), name.fullName())); } private void reportRefToUndefinedName(Name name, Ref ref) { // grab the highest undefined ancestor to output in the warning message. while (name.parent != null && name.parent.globalSets + name.parent.localSets == 0) { name = name.parent; } // If this is an annotated EXPR-GET, don't do anything. Node parent = ref.node.getParent(); if (parent.getType() == Token.EXPR_RESULT) { JSDocInfo info = ref.node.getJSDocInfo(); if (info != null && info.hasTypedefType()) { return; } } compiler.report( JSError.make(ref.sourceName, ref.node, level, UNDEFINED_NAME_WARNING, name.fullName())); } /** * Checks whether the given name is a property, and whether that property * must be initialized with its full qualified name. */ private static boolean propertyMustBeInitializedByFullName(Name name) { // If an object literal in the global namespace is never aliased, // then all of its

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>/* * Copyright 2007 The Closure Compiler Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.javascript.jscomp; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Token; import com.google.javascript.rhino.jstype.FunctionType; import com.google.javascript.rhino.jstype.JSTypeNative; import com.google.javascript.rhino.jstype.ObjectType; import java.util.Collection; import java.util.List; import java.util.Set; /** * This describes the Closure-specific JavaScript coding conventions. * */ public class ClosureCodingConvention extends DefaultCodingConvention { private static final long serialVersionUID = 1L; private static final String TYPEDEF_NAME = "goog.typedef"; static final DiagnosticType OBJECTLIT_EXPECTED = DiagnosticType.warning( "JSC_REFLECT_OBJECTLIT_EXPECTED", "Object literal expected as second argument"); /** * Closure's goog.inherits adds a {@code superClass_} property to the * subclass, and a {@code constructor} property. */ @Override public void applySubclassRelationship(FunctionType parentCtor, FunctionType childCtor, SubclassType type) { if (type == SubclassType.INHERITS) { childCtor.defineDeclaredProperty("superClass_", parentCtor.getPrototype(), false); childCtor.getPrototype().defineDeclaredProperty("constructor", childCtor, false); } } /** * {@inheritDoc} * * <p>Understands several different inheritance patterns that occur in * Google code (various uses of {@code inherits} and {@code mixin}). */ @Override public SubclassRelationship getClassesDefinedByCall(Node callNode) { Node callName = callNode.getFirstChild(); SubclassType type = typeofClassDefiningName(callName); if (type != null) { Node subclass = null; Node superclass = callNode.getLastChild(); // There are six possible syntaxes for a class-defining method: // SubClass.inherits(SuperClass) // goog.inherits(SubClass, SuperClass) // goog$inherits(SubClass, SuperClass

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>) // SubClass.mixin(SuperClass.prototype) // goog.mixin(SubClass.prototype, SuperClass.prototype) // goog$mixin(SubClass.prototype, SuperClass.prototype) if (callNode.getChildCount() == 2 && callName.getType() == Token.GETPROP) { // SubClass.inherits(SuperClass) subclass = callName.getFirstChild(); } else if (callNode.getChildCount() == 3) { // goog.inherits(SubClass, SuperClass) subclass = callName.getNext(); } // bail out if either of the side of the "inherits" // isn't a real class name. This prevents us from // doing something weird in cases like: // goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2) if (subclass != null && subclass.isUnscopedQualifiedName() && superclass.isUnscopedQualifiedName()) { // make sure to strip the prototype off of the nodes // to normalize for goog.mixin return new SubclassRelationship( type, stripPrototype(subclass), stripPrototype(superclass)); } } return null; } /** * Determines whether the given node is a class-defining name, like * "inherits" or "mixin." * @return The type of class-defining name, or null. */ private SubclassType typeofClassDefiningName(Node callName) { // Check if the method name matches one of the class-defining methods. String methodName = null; if (callName.getType() == Token.GETPROP) { methodName = callName.getLastChild().getString(); } else if (callName.getType() == Token.NAME) { String name = callName.getString(); int dollarIndex = name.lastIndexOf('$'); if (dollarIndex != -1) { methodName = name.substring(dollarIndex + 1); } } if (methodName != null) { if (methodName.equals("inherits")) { return SubclassType.INHERITS; } else if (methodName.equals("mixin")) { return SubclassType.MIXIN; } } return null; } @Override public boolean isSuperClassReference(String propertyName) { return "superClass_".equals(propertyName); } /** * Given a qualified name node, strip "prototype" off the end. * * Examples of this transformation: * a.b.c => a.b.c * a.b.c.prototype => a.b.c */ private Node stripPrototype(Node qualifiedName) { if (qualifiedName.getType() == Token.GETPROP && qualifiedName.getLastChild().getString().equals("prototype")) { return qualifiedName.getFirstChild(); } return qualifiedName; } /** * Exctracts X from goog.provide('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> public String extractClassNameIfProvide(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.provide"); } /** * Exctracts X from goog.require('X'), if the applied Node is goog. * * @return The extracted class name, or null. */ @Override public String extractClassNameIfRequire(Node node, Node parent){ return extractClassNameIfGoog(node, parent, "goog.require"); } private static String extractClassNameIfGoog(Node node, Node parent, String functionName){ String className = null; if (NodeUtil.isExprCall(parent)) { Node callee = node.getFirstChild(); if (callee != null && callee.getType() == Token.GETPROP) { String qualifiedName = callee.getQualifiedName(); if ((functionName).equals(qualifiedName)) { className = callee.getNext().getString(); } } } return className; } /** * Use closure's implementation. * @return closure's function name for exporting properties. */ @Override public String getExportPropertyFunction() { return "goog.exportProperty"; } /** * Use closure's implementation. * @return closure's function name for exporting symbols. */ @Override public String getExportSymbolFunction() { return "goog.exportSymbol"; } @Override public List<String> identifyTypeDeclarationCall(Node n) { Node callName = n.getFirstChild(); if ("goog.addDependency".equals(callName.getQualifiedName()) && n.getChildCount() >= 3) { Node typeArray = callName.getNext().getNext(); if (typeArray.getType() == Token.ARRAYLIT) { List<String> typeNames = Lists.newArrayList(); for (Node name = typeArray.getFirstChild(); name != null; name = name.getNext()) { if (name.getType() == Token.STRING) { typeNames.add(name.getString()); } } return typeNames; } } return null; } @Override public String identifyTypeDefAssign(Node n) { Node firstChild = n.getFirstChild(); int type = n.getType(); if (type == Token.ASSIGN) { if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) { return firstChild.getQualifiedName(); } } else if (type == Token.VAR && firstChild.hasChildren()) { if (TYPEDEF_NAME.equals( firstChild.getFirstChild().getQualifiedName())) { return firstChild.getString(); } } return null; } @Override public String getAbstractMethodName() { return "goog.abstractMethod"; } @Override public String getSingletonGetterClassName(Node callNode) { Node callArg = callNode.getFirstChild(); String callName = callArg.getQualifiedName(); // Use both the original name and the post-CollapseProperties name. if (!("goog.add

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>SingletonGetter".equals(callName) || "goog$addSingletonGetter".equals(callName)) || callNode.getChildCount() != 2) { return null; } return callArg.getNext().getQualifiedName(); } @Override public void applySingletonGetter(FunctionType functionType, FunctionType getterType, ObjectType objectType) { functionType.defineDeclaredProperty("getInstance", getterType, false); functionType.defineDeclaredProperty("instance_", objectType, false); } @Override public String getGlobalObject() { return "goog.global"; } private final Set<String> propertyTestFunctions = ImmutableSet.of( "goog.isDef", "goog.isNull", "goog.isDefAndNotNull", "goog.isString", "goog.isNumber", "goog.isBoolean", "goog.isFunction", "goog.isArray", "goog.isObject"); @Override public boolean isPropertyTestFunction(Node call) { Preconditions.checkArgument(call.getType() == Token.CALL); return propertyTestFunctions.contains( call.getFirstChild().getQualifiedName()); } @Override public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t, Node callNode) { Preconditions.checkArgument(callNode.getType() == Token.CALL); Node callName = callNode.getFirstChild(); if (!"goog.reflect.object".equals(callName.getQualifiedName()) || callName.getChildCount() != 2) { return null; } Node typeNode = callName.getNext(); if (!typeNode.isQualifiedName()) { return null; } Node objectNode = typeNode.getNext(); if (objectNode.getType() != Token.OBJECTLIT) { t.getCompiler().report(JSError.make(t.getSourceName(), callNode, OBJECTLIT_EXPECTED)); return null; } return new ObjectLiteralCast(typeNode.getQualifiedName(), typeNode.getNext()); } @Override public boolean isOptionalParameter(Node parameter) { return false; } @Override public boolean isVarArgsParameter(Node parameter) { return false; } @Override public boolean isPrivate(String name) { return false; } @Override public Collection<AssertionFunctionSpec> getAssertionFunctions() { return ImmutableList.<AssertionFunctionSpec>of( new AssertionFunctionSpec("goog.asserts.assert"), new AssertionFunctionSpec("goog.asserts.assertNumber", JSTypeNative.NUMBER_TYPE), new AssertionFunctionSpec("goog.asserts.assertString", JSTypeNative.STRING_TYPE), new AssertionFunctionSpec("goog.asserts.assertFunction", JSTypeNative.FUNCTION_INSTANCE_TYPE), new AssertionFunctionSpec("goog.asserts.assertObject", JSTypeNative.OBJECT_TYPE), new AssertionFunctionSpec("goog.asserts.assertArray", JSTypeNative.ARRAY_TYPE), // TODO(agrieve): It would be better if this could make the first // parameter the type

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Node) node).getDouble()); } private double number; } private static class StringNode extends Node { private static final long serialVersionUID = 1L; StringNode(int type, String str) { super(type); if (null == str) { throw new IllegalArgumentException("StringNode: str is null"); } this.str = str; } StringNode(int type, String str, int lineno, int charno) { super(type, lineno, charno); if (null == str) { throw new IllegalArgumentException("StringNode: str is null"); } this.str = str; } /** * returns the string content. * @return non null. */ @Override public String getString() { return this.str; } /** * sets the string content. * @param str the new value. Non null. */ @Override public void setString(String str) { if (null == str) { throw new IllegalArgumentException("StringNode: str is null"); } this.str = str; } @Override public boolean isEquivalentTo(Node node) { return (node instanceof StringNode && this.str.equals(((StringNode) node).str)); } /** * If the property is not defined, this was not a quoted key. The * QUOTED_PROP int property is only assigned to STRING tokens used as * object lit keys. * @return true if this was a quoted string key in an object literal. */ @Override public boolean isQuotedString() { return getBooleanProp(QUOTED_PROP); } /** * This should only be called for STRING nodes created in object lits. */ @Override public void setQuotedString() { putBooleanProp(QUOTED_PROP, true); } private String str; } // PropListItems are immutable so that they can be shared. private static class PropListItem implements Serializable { private static final long serialVersionUID = 1L; final PropListItem next; final int type; final int intValue; final Object objectValue; PropListItem(int type, int intValue, PropListItem next) { this(type, intValue, null, next); } PropListItem(int type, Object objectValue, PropListItem next) { this(type, 0, objectValue, next); } PropListItem( int type, int intValue, Object objectValue, PropListItem next) { this.type = type; this.intValue = intValue; this.objectValue = objectValue; this.next = next; } } public Node(int nodeType) { type = nodeType; parent = null; sourcePosition = -1; } public Node(int nodeType, Node child) { Preconditions.checkArgument(child.parent == null, "new child has existing parent"); Preconditions.checkArgument(child.next == null, "new child has existing sibling"); type

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>nodeType, left, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node right, int lineno, int charno) { this(nodeType, left, mid, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node left, Node mid, Node mid2, Node right, int lineno, int charno) { this(nodeType, left, mid, mid2, right); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children, int lineno, int charno) { this(nodeType, children); sourcePosition = mergeLineCharNo(lineno, charno); } public Node(int nodeType, Node[] children) { this.type = nodeType; parent = null; if (children.length != 0) { this.first = children[0]; this.last = children[children.length - 1]; for (int i = 1; i < children.length; i++) { if (null != children[i - 1].next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } children[i - 1].next = children[i]; Preconditions.checkArgument(children[i - 1].parent == null); children[i - 1].parent = this; } Preconditions.checkArgument(children[children.length - 1].parent == null); children[children.length - 1].parent = this; if (null != this.last.next) { // fail early on loops. implies same node in array twice throw new IllegalArgumentException("duplicate child"); } } } public static Node newNumber(double number) { return new NumberNode(number); } public static Node newNumber(double number, int lineno, int charno) { return new NumberNode(number, lineno, charno); } public static Node newString(String str) { return new StringNode(Token.STRING, str); } public static Node newString(int type, String str) { return new StringNode(type, str); } public static Node newString(String str, int lineno, int charno) { return new StringNode(Token.STRING, str, lineno, charno); } public static Node newString(int type, String str, int lineno, int charno) { return new StringNode(type, str, lineno, charno); } public int getType() { return type; } public void setType(int type) { this.type = type; } public boolean hasChildren() { return first != null; } public Node getFirstChild() { return first; } public Node getLastChild() { return last; } public Node getNext() { return next; }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> public int getLineno() { return extractLineno(sourcePosition); } public int getCharno() { return extractCharno(sourcePosition); } /** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */ public double getDouble() throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException(this + " is not a number node"); } } /** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */ public void setDouble(double s) throws UnsupportedOperationException { if (this.getType() == Token.NUMBER) { throw new IllegalStateException( "Number node not created with Node.newNumber"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } /** Can only be called when node has String context. */ public String getString() throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } /** Can only be called when node has String context. */ public void setString(String s) throws UnsupportedOperationException { if (this.getType() == Token.STRING) { throw new IllegalStateException( "String node not created with Node.newString"); } else { throw new UnsupportedOperationException(this + " is not a string node"); } } @Override public String toString() { return toString(true, true, true); } public String toString( boolean printSource, boolean printAnnotations, boolean printType) { if (Token.printTrees) { StringBuilder sb = new StringBuilder(); toString(sb, printSource, printAnnotations, printType); return sb.toString(); } return String.valueOf(type); } private void toString( StringBuilder sb, boolean printSource, boolean printAnnotations, boolean printType) { if (Token.printTrees) { sb.append(Token.name(type)); if (this instanceof StringNode) { sb.append(' '); sb.append(getString()); } else if (type == Token.FUNCTION) { sb.append(' '); // In the case of JsDoc trees, the first child is often not a string // which causes exceptions to be thrown when calling toString or // toStringTree. if (first.getType() == Token.STRING) { sb.append(first.getString()); } } else if (this instanceof ScriptOrFnNode) { ScriptOrFnNode sof = (ScriptOrFnNode) this; if (this instanceof FunctionNode) { FunctionNode fn = (FunctionNode) this; sb.append(' '); sb.append(fn.getFunctionName()); } if (printSource) { sb.append(" [source name: "); sb.append(

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>.getChildCount() && getClass() == node2.getClass() && JSType.isEquivalent(jsType, node2.getJSType())) { eq = this.isEquivalentTo(node2); } if (!eq) { return new NodeMismatch(this, node2); } NodeMismatch res = null; Node n, n2; for (n = first, n2 = node2.first; res == null && n != null; n = n.next, n2 = n2.next) { res = n.checkTreeTypeAwareEqualsImpl(n2); if (res != null) { return res; } } return res; } public static String tokenToName(int token) { switch (token) { case Token.ERROR: return "error"; case Token.EOF: return "eof"; case Token.EOL: return "eol"; case Token.ENTERWITH: return "enterwith"; case Token.LEAVEWITH: return "leavewith"; case Token.RETURN: return "return"; case Token.GOTO: return "goto"; case Token.IFEQ: return "ifeq"; case Token.IFNE: return "ifne"; case Token.SETNAME: return "setname"; case Token.BITOR: return "bitor"; case Token.BITXOR: return "bitxor"; case Token.BITAND: return "bitand"; case Token.EQ: return "eq"; case Token.NE: return "ne"; case Token.LT: return "lt"; case Token.LE: return "le"; case Token.GT: return "gt"; case Token.GE: return "ge"; case Token.LSH: return "lsh"; case Token.RSH: return "rsh"; case Token.URSH: return "ursh"; case Token.ADD: return "add"; case Token.SUB: return "sub"; case Token.MUL: return "mul"; case Token.DIV: return "div"; case Token.MOD: return "mod"; case Token.BITNOT: return "bitnot"; case Token.NEG: return "neg"; case Token.NEW: return "new"; case Token.DELPROP: return "delprop"; case Token.TYPEOF: return "typeof"; case Token.GETPROP: return "getprop"; case Token.SETPROP: return "setprop"; case Token.GETELEM: return "getelem"; case Token.SETELEM: return "setelem"; case Token.CALL: return "call"; case Token.NAME: return "name"; case Token.NUMBER: return "number"; case Token.STRING: return "string"; case Token.NULL: return "null"; case Token.THIS: return "this"; case Token

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>continue"; case Token.VAR: return "var"; case Token.WITH: return "with"; case Token.CATCH: return "catch"; case Token.FINALLY: return "finally"; case Token.RESERVED: return "reserved"; case Token.NOT: return "not"; case Token.VOID: return "void"; case Token.BLOCK: return "block"; case Token.ARRAYLIT: return "arraylit"; case Token.OBJECTLIT: return "objectlit"; case Token.LABEL: return "label"; case Token.TARGET: return "target"; case Token.LOOP: return "loop"; case Token.EXPR_VOID: return "expr_void"; case Token.EXPR_RESULT: return "expr_result"; case Token.JSR: return "jsr"; case Token.SCRIPT: return "script"; case Token.EMPTY: return "empty"; case Token.GET_REF: return "get_ref"; case Token.REF_SPECIAL: return "ref_special"; } return "<unknown="+token+">"; } /** Returns true if this node is equivalent semantically to another */ public boolean isEquivalentTo(Node node) { if (type == Token.ARRAYLIT) { try { int[] indices1 = (int[]) getProp(Node.SKIP_INDEXES_PROP); int[] indices2 = (int[]) node.getProp(Node.SKIP_INDEXES_PROP); if (indices1 == null) { if (indices2 != null) { return false; } } else if (indices2 == null) { return false; } else if (indices1.length != indices2.length) { return false; } else { for (int i = 0; i < indices1.length; i++) { if (indices1[i] != indices2[i]) { return false; } } } } catch (Exception e) { return false; } } else if (type == Token.INC || type == Token.DEC) { int post1 = this.getIntProp(INCRDECR_PROP); int post2 = node.getIntProp(INCRDECR_PROP); if (post1 != post2) { return false; } } else if (type == Token.STRING) { int quoted1 = this.getIntProp(QUOTED_PROP); int quoted2 = node.getIntProp(QUOTED_PROP); if (quoted1 != quoted2) { return false; } } return true; } public boolean hasSideEffects() { switch (type) { case Token.EXPR_VOID: case Token.COMMA: if (last != null) return last.hasSideEffects(); else return true; case Token.HOOK: if (first == null || first.next == null || first.next.next == null) { Kit.code

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>Bug(); } return first.next.hasSideEffects() && first.next.next.hasSideEffects(); case Token.ERROR: // Avoid cascaded error messages case Token.EXPR_RESULT: case Token.ASSIGN: case Token.ASSIGN_ADD: case Token.ASSIGN_SUB: case Token.ASSIGN_MUL: case Token.ASSIGN_DIV: case Token.ASSIGN_MOD: case Token.ASSIGN_BITOR: case Token.ASSIGN_BITXOR: case Token.ASSIGN_BITAND: case Token.ASSIGN_LSH: case Token.ASSIGN_RSH: case Token.ASSIGN_URSH: case Token.ENTERWITH: case Token.LEAVEWITH: case Token.RETURN: case Token.GOTO: case Token.IFEQ: case Token.IFNE: case Token.NEW: case Token.DELPROP: case Token.SETNAME: case Token.SETPROP: case Token.SETELEM: case Token.CALL: case Token.THROW: case Token.RETHROW: case Token.SETVAR: case Token.CATCH_SCOPE: case Token.RETURN_RESULT: case Token.SET_REF: case Token.DEL_REF: case Token.REF_CALL: case Token.TRY: case Token.SEMI: case Token.INC: case Token.DEC: case Token.EXPORT: case Token.IMPORT: case Token.IF: case Token.ELSE: case Token.SWITCH: case Token.WHILE: case Token.DO: case Token.FOR: case Token.BREAK: case Token.CONTINUE: case Token.VAR: case Token.CONST: case Token.WITH: case Token.CATCH: case Token.FINALLY: case Token.BLOCK: case Token.LABEL: case Token.TARGET: case Token.LOOP: case Token.JSR: case Token.SETPROP_OP: case Token.SETELEM_OP: case Token.LOCAL_BLOCK: case Token.SET_REF_OP: return true; default: return false; } } /** * This function takes a set of GETPROP nodes and produces a string that is * each property separated by dots. If the node ultimately under the left * sub-tree is not a simple name, this is not a valid qualified name. * * @return a null if this is not a qualified name, or a dot-separated string * of the name and properties. */ public String getQualifiedName() { if (type == Token.NAME) { return getString(); } else if (type == Token.GETPROP) { String left = getFirstChild().getQualifiedName(); if (left == null) { return null; } return left + "." + getLastChild().getString(); } else if (type == Token.THIS) { return "this"; } else { return null; } } /** * Returns

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> whether a node corresponds to a simple or a qualified name, such as * <code>x</code> or <code>a.b.c</code> or <code>this.a</code>. */ public boolean isQualifiedName() { switch (getType()) { case Token.NAME: case Token.THIS: return true; case Token.GETPROP: return getFirstChild().isQualifiedName(); default: return false; } } /** * Returns whether a node corresponds to a simple or a qualified name without * a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code> * . */ public boolean isUnscopedQualifiedName() { switch (getType()) { case Token.NAME: return true; case Token.GETPROP: return getFirstChild().isUnscopedQualifiedName(); default: return false; } } // ========================================================================== // Mutators /** * Removes this node from its parent. Equivalent to: * node.getParent().removeChild(); */ public Node detachFromParent() { Preconditions.checkState(parent != null); parent.removeChild(this); return this; } /** * Removes the first child of Node. Equivalent to: * node.removeChild(node.getFirstChild()); * * @return The removed Node. */ public Node removeFirstChild() { Node child = first; if (child != null) { removeChild(child); } return child; } /** * @return A Node that is the head of the list of children. */ public Node removeChildren() { Node children = first; for (Node child = first; child != null; child = child.getNext()) { child.parent = null; } first = null; last = null; return children; } /** * Removes all children from this node and isolates the children from each * other. */ public void detachChildren() { for (Node child = first; child != null;) { Node nextChild = child.getNext(); child.parent = null; child.next = null; child = nextChild; } first = null; last = null; } public Node removeChildAfter(Node prev) { Preconditions.checkArgument(prev.parent == this, "prev is not a child of this node."); Preconditions.checkArgument(prev.next != null, "no next sibling."); Node child = prev.next; prev.next = child.next; if (child == last) last = prev; child.next = null; child.parent = null; return child; } /** * @return A detached clone of the Node, specifically excluding its children. */ public Node cloneNode() { Node result; try { result = (Node) super.clone(); // PropListItem lists are immutable and can be shared so there is no // need to

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS><String>) getProp(DIRECTIVES); } /** * Adds a warning to be suppressed. This is indistinguishable * from having a {@code @suppress} tag in the code. */ public void addSuppression(String warning) { if (getJSDocInfo() == null) { setJSDocInfo(new JSDocInfo(false)); } getJSDocInfo().addSuppression(warning); } /** * Sets whether this is a synthetic block that should not be considered * a real source block. */ public void setWasEmptyNode(boolean val) { putBooleanProp(EMPTY_BLOCK, val); } /** * Returns whether this is a synthetic block that should not be considered * a real source block. */ public boolean wasEmptyNode() { return getBooleanProp(EMPTY_BLOCK); } // There are four values of interest: // global state changes // this state changes // arguments state changes // whether the call throws an exception // locality of the result // We want a value of 0 to mean "global state changes and // unknown locality of result". final public static int FLAG_GLOBAL_STATE_UNMODIFIED = 1; final public static int FLAG_THIS_UNMODIFIED = 2; final public static int FLAG_ARGUMENTS_UNMODIFIED = 4; final public static int FLAG_NO_THROWS = 8; final public static int FLAG_LOCAL_RESULTS = 16; final public static int SIDE_EFFECTS_FLAGS_MASK = 31; final public static int SIDE_EFFECTS_ALL = 0; final public static int NO_SIDE_EFFECTS = FLAG_GLOBAL_STATE_UNMODIFIED | FLAG_THIS_UNMODIFIED | FLAG_ARGUMENTS_UNMODIFIED | FLAG_NO_THROWS; /** * Marks this function or constructor call's side effect flags. * This property is only meaningful for {@link Token#CALL} and * {@link Token#NEW} nodes. */ public void setSideEffectFlags(int flags) { Preconditions.checkArgument( getType() == Token.CALL || getType() == Token.NEW, "setIsNoSideEffectsCall only supports CALL and NEW nodes, got " + Token.name(getType())); putIntProp(SIDE_EFFECT_FLAGS, flags); } public void setSideEffectFlags(SideEffectFlags flags) { setSideEffectFlags(flags.valueOf()); } /** * Returns the side effects flags for this node. */ public int getSideEffectFlags() { return getIntProp(SIDE_EFFECT_FLAGS); } /** * A helper class for getting and setting the side-effect flags. * @author johnlenz@google.com (John Lenz) */ public static class SideEffectFlags { private int value = Node.SIDE_EFFECTS_ALL; public SideEffectFlags() {

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>. return getNativeType(ARRAY_TYPE); case Token.PIPE: // Union type UnionTypeBuilder builder = new UnionTypeBuilder(this); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { builder.addAlternate( createFromTypeNodesInternal(child, sourceName, scope, false)); } return builder.build(); case Token.EMPTY: // When the return value of a function is not specified return getNativeType(UNKNOWN_TYPE); case Token.VOID: // Only allowed in the return value of a function. return getNativeType(VOID_TYPE); case Token.STRING: JSType namedType = getType(scope, n.getString(), sourceName, n.getLineno(), n.getCharno()); if (forgiving) { namedType.forgiveUnknownNames(); } if (resolveMode != ResolveMode.LAZY_NAMES) { namedType = namedType.resolveInternal(reporter, scope); } if ((namedType instanceof ObjectType) && !(nonNullableTypeNames.contains(n.getString()))) { Node typeList = n.getFirstChild(); if (typeList != null && ("Array".equals(n.getString()) || "Object".equals(n.getString()))) { JSType parameterType = createFromTypeNodesInternal( typeList.getLastChild(), sourceName, scope, false); namedType = new ParameterizedType( this, (ObjectType) namedType, parameterType); if (typeList.hasMoreThanOneChild()) { JSType indexType = createFromTypeNodesInternal( typeList.getFirstChild(), sourceName, scope, false); namedType = new IndexedType( this, (ObjectType) namedType, indexType); } } return createDefaultObjectUnion(namedType); } else { return namedType; } case Token.FUNCTION: ObjectType thisType = null; boolean isConstructor = false; Node current = n.getFirstChild(); if (current.getType() == Token.THIS || current.getType() == Token.NEW) { Node contextNode = current.getFirstChild(); thisType = ObjectType.cast( createFromTypeNodesInternal( contextNode, sourceName, scope, false) .restrictByNotNullOrUndefined()); if (thisType == null) { reporter.warning( ScriptRuntime.getMessage0( current.getType() == Token.THIS ? "msg.jsdoc.function.thisnotobject" : "msg.jsdoc.function.newnotobject"), sourceName, contextNode.getLineno(), "", contextNode.getCharno()); } isConstructor = current.getType() == Token.NEW; current = current.getNext(); } FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this); if (current.getType() == Token.LP) { Node args = current.getFirstChild(); for (Node arg = current.getFirstChild(); arg != null; arg = arg.getNext

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>()) { if (arg.getType() == Token.ELLIPSIS) { if (arg.getChildCount() == 0) { paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE)); } else { paramBuilder.addVarArgs( createFromTypeNodesInternal( arg.getFirstChild(), sourceName, scope, false)); } } else { JSType type = createFromTypeNodesInternal( arg, sourceName, scope, false); if (arg.getType() == Token.EQUALS) { boolean addSuccess = paramBuilder.addOptionalParams(type); if (!addSuccess) { reporter.warning( ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"), sourceName, arg.getLineno(), "", arg.getCharno()); } } else { paramBuilder.addRequiredParams(type); } } } current = current.getNext(); } JSType returnType = createFromTypeNodesInternal(current, sourceName, scope, false); return new FunctionBuilder(this) .withParams(paramBuilder) .withReturnType(returnType) .withTypeOfThis(thisType) .setIsConstructor(isConstructor) .build(); } throw new IllegalStateException( "Unexpected node in type expression: " + n.toString()); } /** * Creates a RecordType from the nodes representing said record type. * @param n The node with type info. * @param sourceName The source file name. * @param scope A scope for doing type name lookups. */ private JSType createRecordTypeFromNodes(Node n, String sourceName, StaticScope<JSType> scope) { RecordTypeBuilder builder = new RecordTypeBuilder(this); // For each of the fields in the record type. for (Node fieldTypeNode = n.getFirstChild(); fieldTypeNode != null; fieldTypeNode = fieldTypeNode.getNext()) { // Get the property's name. Node fieldNameNode = fieldTypeNode; boolean hasType = false; if (fieldTypeNode.getType() == Token.COLON) { fieldNameNode = fieldTypeNode.getFirstChild(); hasType = true; } String fieldName = fieldNameNode.getString(); // TODO(user): Move this into the lexer/parser. // Remove the string literal characters around a field name, // if any. if (fieldName.startsWith("'") || fieldName.startsWith("\"")) { fieldName = fieldName.substring(1, fieldName.length() - 1); } // Get the property's type. JSType fieldType = null; if (hasType) { // We have a declared type. fieldType = createFromTypeNodesInternal( fieldTypeNode.getLastChild(), sourceName, scope, false); } else { // Otherwise, the type is UNKNOWN. fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE); } // Add the property to the record. builder.addProperty(fieldName, fieldType); } return builder.build(); } /**

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> represents the condition check for each iteration. // That way the following: // for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to // var x = 0; while(x<10) { x++; } if (NodeUtil.isForIn(parent)) { return n == parent.getLastChild(); } else { return NodeUtil.getConditionExpression(parent) != n; } case Token.SWITCH: case Token.CASE: case Token.CATCH: case Token.WITH: return n != parent.getFirstChild(); default: return false; } } }

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>. */ public Scope createScope(Node root, Scope parent) { // Constructing the global scope is very different than constructing // inner scopes, because only global scopes can contain named classes that // show up in the type registry. Scope newScope = null; AbstractScopeBuilder scopeBuilder = null; if (parent == null) { // Find all the classes in the global scope. newScope = createInitialScope(root); GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope); scopeBuilder = globalScopeBuilder; NodeTraversal.traverse(compiler, root, scopeBuilder); } else { newScope = new Scope(parent, root); LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope); scopeBuilder = localScopeBuilder; localScopeBuilder.build(); } scopeBuilder.resolveStubDeclarations(); scopeBuilder.resolveTypes(); // Gather the properties in each function that we found in the // global scope, if that function has a @this type that we can // build properties on. for (Node functionNode : scopeBuilder.nonExternFunctions) { JSType type = functionNode.getJSType(); if (type != null && type instanceof FunctionType) { FunctionType fnType = (FunctionType) type; ObjectType fnThisType = fnType.getTypeOfThis(); if (!fnThisType.isUnknownType()) { NodeTraversal.traverse(compiler, functionNode.getLastChild(), scopeBuilder.new CollectProperties(fnThisType)); } } } if (parent == null) { codingConvention.defineDelegateProxyPrototypeProperties( typeRegistry, newScope, delegateProxyPrototypes); } return newScope; } /** * Create the outermost scope. This scope contains native binding such as * {@code Object}, {@code Date}, etc. */ @VisibleForTesting Scope createInitialScope(Node root) { NodeTraversal.traverse( compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry)); Scope s = new Scope(root, compiler); declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE); declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, DATE_FUNCTION_TYPE); declareNativeFunctionType(s, ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE); declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE); declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE); declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE); declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> declareNativeValueType(s, "undefined", VOID_TYPE); // The typedef construct needs the any type, so that it can be assigned // to anything. This is kind of a hack, and an artifact of the typedef // syntax we've chosen. declareNativeValueType(s, LEGACY_TYPEDEF, NO_TYPE); // ActiveXObject is unqiuely special, because it can be used to construct // any type (the type that it creates is related to the arguments you // pass to it). declareNativeValueType(s, "ActiveXObject", NO_OBJECT_TYPE); return s; } private void declareNativeFunctionType(Scope scope, JSTypeNative tId) { FunctionType t = typeRegistry.getNativeFunctionType(tId); declareNativeType(scope, t.getInstanceType().getReferenceName(), t); declareNativeType( scope, t.getPrototype().getReferenceName(), t.getPrototype()); } private void declareNativeValueType(Scope scope, String name, JSTypeNative tId) { declareNativeType(scope, name, typeRegistry.getNativeType(tId)); } private void declareNativeType(Scope scope, String name, JSType t) { scope.declare(name, null, t, null, false); } private static class DiscoverEnumsAndTypedefs extends AbstractShallowStatementCallback { private final JSTypeRegistry registry; DiscoverEnumsAndTypedefs(JSTypeRegistry registry) { this.registry = registry; } @Override public void visit(NodeTraversal t, Node node, Node parent) { Node nameNode = null; switch (node.getType()) { case Token.VAR: for (Node child = node.getFirstChild(); child != null; child = child.getNext()) { identifyNameNode( child, child.getFirstChild(), NodeUtil.getInfoForNameNode(child)); } break; case Token.EXPR_RESULT: Node firstChild = node.getFirstChild(); if (firstChild.getType() == Token.ASSIGN) { identifyNameNode( firstChild.getFirstChild(), firstChild.getLastChild(), firstChild.getJSDocInfo()); } else { identifyNameNode( firstChild, null, firstChild.getJSDocInfo()); } break; } } private void identifyNameNode( Node nameNode, Node valueNode, JSDocInfo info) { if (nameNode.isQualifiedName()) { if (info != null) { if (info.hasEnumParameterType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } else if (info.hasTypedefType()) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } if (valueNode != null && LEGACY_TYPEDEF.equals(valueNode.getQualifiedName())) { registry.identifyNonNullableName(nameNode.getQualifiedName()); } } } } /** * Given a node, determines whether that node names a prototype

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> * property, and if so, returns the qualified name node representing * the owner of that property. Otherwise, returns null. */ private static Node getPrototypePropertyOwner(Node n) { if (n.getType() == Token.GETPROP) { Node firstChild = n.getFirstChild(); if (firstChild.getType() == Token.GETPROP && firstChild.getLastChild().getString().equals("prototype")) { Node maybeOwner = firstChild.getFirstChild(); if (maybeOwner.isQualifiedName()) { return maybeOwner; } } } return null; } private JSType getNativeType(JSTypeNative nativeType) { return typeRegistry.getNativeType(nativeType); } private abstract class AbstractScopeBuilder implements NodeTraversal.Callback { /** * The scope that we're builidng. */ final Scope scope; private final List<DeferredSetType> deferredSetTypes = Lists.newArrayList(); /** * Functions that we found in the global scope and not in externs. */ private final List<Node> nonExternFunctions = Lists.newArrayList(); /** * Type-less stubs. * * If at the end of traversal, we still don't have types for these * stubs, then we should declare UNKNOWN types. */ private final List<StubDeclaration> stubDeclarations = Lists.newArrayList(); /** * The current source file that we're in. */ private String sourceName = null; private AbstractScopeBuilder(Scope scope) { this.scope = scope; } void setDeferredType(Node node, JSType type) { deferredSetTypes.add(new DeferredSetType(node, type)); } void resolveTypes() { // Resolve types and attach them to nodes. for (DeferredSetType deferred : deferredSetTypes) { deferred.resolve(scope); } // Resolve types and attach them to scope slots. Iterator<Var> vars = scope.getVars(); while (vars.hasNext()) { vars.next().resolveType(typeParsingErrorReporter); } // Tell the type registry that any remaining types // are unknown. typeRegistry.resolveTypesInScope(scope); } @Override public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { if (n.getType() == Token.FUNCTION || n.getType() == Token.SCRIPT) { sourceName = (String) n.getProp(Node.SOURCENAME_PROP); } // We do want to traverse the name of a named function, but we don't // want to traverse the arguments or body. return parent == null || parent.getType() != Token.FUNCTION || n == parent.getFirstChild() || parent == scope.getRootNode(); } @Override public void visit(NodeTraversal t, Node n, Node parent) { attachLiteralTypes(n); switch (n.getType()) { case Token.CALL: checkForClassDefiningCalls(t, n, parent

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>); break; case Token.FUNCTION: if (t.getInput() == null || !t.getInput().isExtern()) { nonExternFunctions.add(n); } // VARs and ASSIGNs are handled in different branches of this // switch statement. if (parent.getType() != Token.ASSIGN && parent.getType() != Token.NAME) { defineDeclaredFunction(n, parent); } break; case Token.ASSIGN: // Handle constructor and enum definitions. defineNamedTypeAssign(n, parent); // Handle initialization of properties. Node firstChild = n.getFirstChild(); if (firstChild.getType() == Token.GETPROP && firstChild.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), firstChild, n, firstChild.getNext()); } break; case Token.CATCH: defineCatch(n, parent); break; case Token.VAR: defineVar(n, parent); break; case Token.GETPROP: // Handle stubbed properties. if (parent.getType() == Token.EXPR_RESULT && n.isQualifiedName()) { maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null); } break; } } private void attachLiteralTypes(Node n) { switch (n.getType()) { case Token.NULL: n.setJSType(getNativeType(NULL_TYPE)); break; case Token.VOID: n.setJSType(getNativeType(VOID_TYPE)); break; case Token.STRING: n.setJSType(getNativeType(STRING_TYPE)); break; case Token.NUMBER: n.setJSType(getNativeType(NUMBER_TYPE)); break; case Token.TRUE: case Token.FALSE: n.setJSType(getNativeType(BOOLEAN_TYPE)); break; case Token.REGEXP: n.setJSType(getNativeType(REGEXP_TYPE)); break; case Token.REF_SPECIAL: n.setJSType(getNativeType(UNKNOWN_TYPE)); break; case Token.OBJECTLIT: processObjectLit(n); break; // NOTE(nicksantos): If we ever support Array tuples, // we will need to put ARRAYLIT here as well. } } private void processObjectLit(Node objectLit) { JSDocInfo info = objectLit.getJSDocInfo(); if (info != null && info.getLendsName() != null) { String lendsName = info.getLendsName(); Var lendsVar = scope.getVar(lendsName); if (lendsVar == null) { compiler.report( JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName)); } else { JSType type = lendsVar.getType(); if (type == null) { type = typeRegistry

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>.getNativeType(UNKNOWN_TYPE); } if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) { compiler.report( JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT, lendsName, type.toString())); } else { objectLit.setJSType(type); } } } if (objectLit.getJSType() == null) { objectLit.setJSType(typeRegistry.createAnonymousObjectType()); } } /** * Returns the type specified in a JSDoc annotation near a GETPROP or NAME. * * Extracts type information from either the {@code @type} tag or from * the {@code @return} and {@code @param} tags. */ JSType getDeclaredTypeInAnnotation( NodeTraversal t, Node node, JSDocInfo info) { return getDeclaredTypeInAnnotation(t.getSourceName(), node, info); } JSType getDeclaredTypeInAnnotation(String sourceName, Node node, JSDocInfo info) { JSType jsType = null; Node objNode = node.getType() == Token.GETPROP ? node.getFirstChild() : null; if (info != null) { if (info.hasType()) { jsType = info.getType().evaluate(scope, typeRegistry); } else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) { String fnName = node.getQualifiedName(); // constructors are often handled separately. if (info.isConstructor() && typeRegistry.getType(fnName) != null) { return null; } FunctionTypeBuilder builder = new FunctionTypeBuilder( fnName, compiler, node, sourceName, scope) .inferTemplateTypeName(info) .inferReturnType(info) .inferParameterTypes(info) .inferInheritance(info); // Infer the context type. boolean searchedForThisType = false; if (objNode != null) { if (objNode.getType() == Token.GETPROP && objNode.getLastChild().getString().equals("prototype")) { builder.inferThisType(info, objNode.getFirstChild()); searchedForThisType = true; } else if (objNode.getType() == Token.THIS) { builder.inferThisType(info, objNode.getJSType()); searchedForThisType = true; } } if (!searchedForThisType) { builder.inferThisType(info, (Node) null); } jsType = builder.buildAndRegister(); } } return jsType; } /** * Asserts that it's ok to define this node's name. * The node should have a source name and be of the specified type. */ void assertDefinitionNode(Node n, int type) { Preconditions.checkState(sourceName != null); Preconditions.checkState(n.getType() == type); } /** * Defines a catch parameter. */

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> void defineCatch(Node n, Node parent) { assertDefinitionNode(n, Token.CATCH); Node catchName = n.getFirstChild(); defineSlot(catchName, n, null); } /** * Defines a VAR initialization. */ void defineVar(Node n, Node parent) { assertDefinitionNode(n, Token.VAR); JSDocInfo info = n.getJSDocInfo(); if (n.hasMoreThanOneChild()) { if (info != null) { // multiple children compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF)); } for (Node name : n.children()) { defineName(name, n, parent, name.getJSDocInfo()); } } else { Node name = n.getFirstChild(); defineName(name, n, parent, (info != null) ? info : name.getJSDocInfo()); } } /** * Defines a declared function. */ void defineDeclaredFunction(Node n, Node parent) { assertDefinitionNode(n, Token.FUNCTION); JSDocInfo info = n.getJSDocInfo(); int parentType = parent.getType(); Preconditions.checkState( (scope.isLocal() || parentType != Token.ASSIGN) && parentType != Token.NAME, "function defined as standalone function when it is being " + "assigned"); String functionName = n.getFirstChild().getString(); FunctionType functionType = getFunctionType(functionName, n, info, null); if (NodeUtil.isFunctionDeclaration(n)) { defineSlot(n.getFirstChild(), n, functionType); } } /** * Defines a qualified name assign to an enum or constructor. */ void defineNamedTypeAssign(Node n, Node parent) { assertDefinitionNode(n, Token.ASSIGN); JSDocInfo info = n.getJSDocInfo(); // TODO(nicksantos): We should support direct assignment to a // prototype, as in: // Foo.prototype = { // a: function() { ... }, // b: function() { ... } // }; // Right now (6/23/08), we understand most of this syntax, but we // don't tie the "a" and "b" methods to the context of Foo. Node rvalue = n.getLastChild(); Node lvalue = n.getFirstChild(); info = (info != null) ? info : rvalue.getJSDocInfo(); if (rvalue.getType() == Token.FUNCTION || info != null && info.isConstructor()) { getFunctionType(lvalue.getQualifiedName(), rvalue, info, lvalue); } else if (info != null && info.hasEnumParameterType()) { JSType type = getEnumType(lvalue.getQualifiedName(), n, rvalue, info.getEnumParameterType().evaluate(scope, typeRegistry)); if (type != null) {

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> setDeferredType(lvalue, type); } } } /** * Defines a variable based on the {@link Token#NAME} node passed. * @param name The {@link Token#NAME} node. * @param var The parent of the {@code name} node, which must be a * {@link Token#VAR} node. * @param parent {@code var}'s parent. * @param info the {@link JSDocInfo} information relating to this * {@code name} node. */ private void defineName(Node name, Node var, Node parent, JSDocInfo info) { Node value = name.getFirstChild(); if (value != null && value.getType() == Token.FUNCTION) { // function String functionName = name.getString(); FunctionType functionType = getFunctionType(functionName, value, info, null); if (functionType.isReturnTypeInferred() && scope.isLocal()) { defineSlot(name, var, null); } else { defineSlot(name, var, functionType); } } else { // variable's type JSType type = null; if (info == null) { // the variable's type will be inferred CompilerInput input = compiler.getInput(sourceName); Preconditions.checkNotNull(input, sourceName); type = input.isExtern() ? getNativeType(UNKNOWN_TYPE) : null; } else if (info.hasEnumParameterType()) { type = getEnumType(name.getString(), var, value, info.getEnumParameterType().evaluate(scope, typeRegistry)); } else if (info.isConstructor()) { type = getFunctionType(name.getString(), value, info, name); } else { type = getDeclaredTypeInAnnotation(sourceName, name, info); } defineSlot(name, var, type); } } /** * Gets the function type from the function node and its attached * {@link JSDocInfo}. * @param name the function's name * @param rValue the function node. It must be a {@link Token#FUNCTION}. * @param info the {@link JSDocInfo} attached to the function definition * @param lvalueNode The node where this function is being * assigned. For example, {@code A.prototype.foo = ...} would be used to * determine that this function is a method of A.prototype. May be * null to indicate that this is not being assigned to a qualified name. */ private FunctionType getFunctionType(String name, Node rValue, JSDocInfo info, @Nullable Node lvalueNode) { FunctionType functionType = null; // Global function aliases should be registered with the type registry. if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) { Var var = scope.getVar(rValue.getQualifiedName()); if (var != null && var.getType() instanceof FunctionType) { functionType = (FunctionType)

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> var.getType(); if (functionType != null && (functionType.isConstructor() || functionType.isInterface())) { typeRegistry.declareType(name, functionType.getInstanceType()); } } return functionType; } Node owner = null; if (lvalueNode != null) { owner = getPrototypePropertyOwner(lvalueNode); } Node errorRoot = rValue == null ? lvalueNode : rValue; boolean isFnLiteral = rValue != null && rValue.getType() == Token.FUNCTION; Node fnRoot = isFnLiteral ? rValue : null; Node parametersNode = isFnLiteral ? rValue.getFirstChild().getNext() : null; Node fnBlock = isFnLiteral ? parametersNode.getNext() : null; if (functionType == null && info != null && info.hasType()) { JSType type = info.getType().evaluate(scope, typeRegistry); // Known to be not null since we have the FUNCTION token there. type = type.restrictByNotNullOrUndefined(); if (type.isFunctionType()) { functionType = (FunctionType) type; functionType.setJSDocInfo(info); } } if (functionType == null) { // Find the type of any overridden function. FunctionType overriddenPropType = null; if (lvalueNode != null && lvalueNode.getType() == Token.GETPROP && lvalueNode.isQualifiedName()) { Var var = scope.getVar( lvalueNode.getFirstChild().getQualifiedName()); if (var != null) { ObjectType ownerType = ObjectType.cast(var.getType()); if (ownerType != null) { String propName = lvalueNode.getLastChild().getString(); overriddenPropType = findOverriddenFunction(ownerType, propName); } } } functionType = new FunctionTypeBuilder(name, compiler, errorRoot, sourceName, scope) .setSourceNode(fnRoot) .inferFromOverriddenFunction(overriddenPropType, parametersNode) .inferTemplateTypeName(info) .inferReturnType(info) .inferInheritance(info) .inferThisType(info, owner) .inferParameterTypes(parametersNode, info) .inferReturnStatementsAsLastResort(fnBlock) .buildAndRegister(); } // assigning the function type to the function node if (rValue != null) { setDeferredType(rValue, functionType); } // all done return functionType; } /** * Find the function that's being overridden on this type, if any. */ private FunctionType findOverriddenFunction( ObjectType ownerType, String propName) { // First, check to see if the property is implemented // on a superclass. JSType propType = ownerType.getPropertyType(propName); if (propType instanceof FunctionType) { return (FunctionType) propType; } else { // If it's not, then check to see if it's implemented

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> name, Node parent, JSType type) { defineSlot(name, parent, type, type == null); } /** * Defines a typed variable. The defining node will be annotated with the * variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is * inferred. * * Slots may be any variable or any qualified name in the global scope. * * @param n the defining NAME or GETPROP node. * @param parent the {@code n}'s parent. * @param type the variable's type. It may be {@code null} if * {@code inferred} is {@code true}. */ void defineSlot(Node n, Node parent, JSType type, boolean inferred) { Preconditions.checkArgument(inferred || type != null); // Only allow declarations of NAMEs and qualfied names. boolean shouldDeclareOnGlobalThis = false; if (n.getType() == Token.NAME) { Preconditions.checkArgument( parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR || parent.getType() == Token.LP || parent.getType() == Token.CATCH); shouldDeclareOnGlobalThis = scope.isGlobal() && (parent.getType() == Token.VAR || parent.getType() == Token.FUNCTION); } else { Preconditions.checkArgument( n.getType() == Token.GETPROP && (parent.getType() == Token.ASSIGN || parent.getType() == Token.EXPR_RESULT)); } String variableName = n.getQualifiedName(); Preconditions.checkArgument(!variableName.isEmpty()); // If n is a property, then we should really declare it in the // scope where the root object appears. This helps out people // who declare "global" names in an anonymous namespace. Scope scopeToDeclareIn = scope; if (n.getType() == Token.GETPROP && !scope.isGlobal() && isQnameRootedInGlobalScope(n)) { Scope globalScope = scope.getGlobalScope(); // don't try to declare in the global scope if there's // already a symbol there with this name. if (!globalScope.isDeclared(variableName, false)) { scopeToDeclareIn = scope.getGlobalScope(); } } // declared in closest scope? if (scopeToDeclareIn.isDeclared(variableName, false)) { Var oldVar = scopeToDeclareIn.getVar(variableName); validator.expectUndeclaredVariable( sourceName, n, parent, oldVar, variableName, type); } else { if (!inferred) { setDeferredType(n, type); } CompilerInput input = compiler.getInput(sourceName); boolean isExtern = input.isExtern(); Var newVar = scopeToDeclareIn.declare(variableName, n, type, input, inferred); if (shouldDeclareOnGlobalThis) { ObjectType globalThis = typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS); if (

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS>inferred) { globalThis.defineInferredProperty(variableName, type == null ? getNativeType(JSTypeNative.NO_TYPE) : type, isExtern); } else { globalThis.defineDeclaredProperty(variableName, type, isExtern); } } // We need to do some additional work for constructors and interfaces. if (type instanceof FunctionType && // We don't want to look at empty function types. !type.isEmptyType()) { FunctionType fnType = (FunctionType) type; if ((fnType.isConstructor() || fnType.isInterface()) && !fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) { // Declare var.prototype in the scope chain. FunctionType superClassCtor = fnType.getSuperClassConstructor(); scopeToDeclareIn.declare(variableName + ".prototype", n, fnType.getPrototype(), input, /* declared iff there's an explicit supertype */ superClassCtor == null || superClassCtor.getInstanceType().equals( getNativeType(OBJECT_TYPE))); // Make sure the variable is initialized to something if // it constructs itself. if (newVar.getInitialValue() == null && !isExtern && // We want to make sure that when we declare a new instance // type (with @constructor) that there's actually a ctor for it. // This doesn't apply to structural constructors // (like function(new:Array). Checking the constructed // type against the variable name is a sufficient check for // this. variableName.equals( fnType.getInstanceType().getReferenceName())) { compiler.report( JSError.make(sourceName, n, fnType.isConstructor() ? CTOR_INITIALIZER : IFACE_INITIALIZER, variableName)); } } } } } /** * Check if the given node is a property of a name in the global scope. */ private boolean isQnameRootedInGlobalScope(Node n) { Node root = NodeUtil.getRootOfQualifiedName(n); if (root.getType() == Token.NAME) { Var var = scope.getVar(root.getString()); if (var != null) { return var.isGlobal(); } } return false; } /** * Look for a type declaration on a GETPROP node. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ private JSType getDeclaredGetPropType(NodeTraversal t, JSDocInfo info, Node n, @Nullable Node rhsValue) { if (info != null && info.hasType()) { return getDeclaredTypeInAnnotation(t, n, info); } else if (info != null && info

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> null && delegateBaseObject != null && delegateSuperObject != null) { FunctionType delegatorCtor = delegatorObject.getConstructor(); FunctionType delegateBaseCtor = delegateBaseObject.getConstructor(); FunctionType delegateSuperCtor = delegateSuperObject.getConstructor(); if (delegatorCtor != null && delegateBaseCtor != null && delegateSuperCtor != null) { FunctionParamBuilder functionParamBuilder = new FunctionParamBuilder(typeRegistry); functionParamBuilder.addRequiredParams( getNativeType(U2U_CONSTRUCTOR_TYPE)); FunctionType findDelegate = typeRegistry.createFunctionType( typeRegistry.createDefaultObjectUnion(delegateBaseObject), functionParamBuilder.build()); FunctionType delegateProxy = typeRegistry.createConstructorType( delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX, null, null, null); delegateProxy.setPrototypeBasedOn(delegateBaseObject); codingConvention.applyDelegateRelationship( delegateSuperObject, delegateBaseObject, delegatorObject, delegateProxy, findDelegate); delegateProxyPrototypes.add(delegateProxy.getPrototype()); } } } /** * Declare the symbol for a qualified name in the global scope. * * @param info The doc info for this property. * @param n A top-level GETPROP node (it should not be contained inside * another GETPROP). * @param parent The parent of {@code n}. * @param rhsValue The node that {@code n} is being initialized to, * or {@code null} if this is a stub declaration. */ void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { Node ownerNode = n.getFirstChild(); String ownerName = ownerNode.getQualifiedName(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); Preconditions.checkArgument(qName != null && ownerName != null); // Function prototypes are special. // It's a common JS idiom to do: // F.prototype = { ... }; // So if F does not have an explicitly declared super type, // allow F.prototype to be redefined arbitrarily. if ("prototype".equals(propName)) { Var qVar = scope.getVar(qName); if (qVar != null) { if (!qVar.isTypeInferred()) { // Just ignore assigns to declared prototypes. return; } if (qVar.getScope() == scope) { scope.undeclare(qVar); } } } // Precedence of type information on GETPROPs: // 1) @type annnotation / @enum annotation // 2) ASSIGN to FUNCTION literal // 3) @param/@return annotation (with no function literal) // 4) ASSIGN to anything else // // 1 and 3 are declarations, 4 is inferred, and 2 is a declaration iff // the function has not

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> been declared before. // // FUNCTION literals are special because TypedScopeCreator is very smart // about getting as much type information as possible for them. // Determining type for #1 + #2 + #3 JSType valueType = getDeclaredGetPropType(t, info, n, rhsValue); if (valueType == null && rhsValue != null) { // Determining type for #4 valueType = rhsValue.getJSType(); } if (valueType == null) { if (parent.getType() == Token.EXPR_RESULT) { stubDeclarations.add(new StubDeclaration( n, t.getInput() != null && t.getInput().isExtern(), ownerName)); } return; } boolean inferred = true; if (info != null) { // Determining declaration for #1 + #3 inferred = !(info.hasType() || info.hasEnumParameterType() || FunctionTypeBuilder.isFunctionTypeDeclaration(info)); } if (inferred) { // Determining declaration for #2 inferred = !(rhsValue != null && rhsValue.getType() == Token.FUNCTION && !scope.isDeclared(qName, false)); } if (!inferred) { ObjectType ownerType = getObjectSlot(ownerName); if (ownerType != null) { // Only declare this as an official property if it has not been // declared yet. boolean isExtern = t.getInput() != null && t.getInput().isExtern(); if ((!ownerType.hasOwnProperty(propName) || ownerType.isPropertyTypeInferred(propName)) && ((isExtern && !ownerType.isNativeObjectType()) || !ownerType.isInstanceType())) { // If the property is undeclared or inferred, declare it now. ownerType.defineDeclaredProperty(propName, valueType, isExtern); } } // If the property is already declared, the error will be // caught when we try to declare it in the current scope. defineSlot(n, parent, valueType, inferred); } else if (rhsValue != null && rhsValue.getType() == Token.TRUE) { // We declare these for delegate proxy method properties. ObjectType ownerType = getObjectSlot(ownerName); if (ownerType instanceof FunctionType) { JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis(); String delegateName = codingConvention.getDelegateSuperclassName(); JSType delegateType = delegateName == null ? null : typeRegistry.getType(delegateName); if (delegateType != null && ownerTypeOfThis.isSubtype(delegateType)) { defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true); } } } } /** * Find the ObjectType associated with the given slot. * @param slotName The name of the slot to find the type in. * @return An object type, or null if this slot does not contain an object. */ private ObjectType getObjectSlot(String slotName) {

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> Var ownerVar = scope.getVar(slotName); if (ownerVar != null) { JSType ownerVarType = ownerVar.getType(); return ObjectType.cast(ownerVarType == null ? null : ownerVarType.restrictByNotNullOrUndefined()); } return null; } /** * Resolve any stub delcarations to unknown types if we could not * find types for them during traversal. */ void resolveStubDeclarations() { for (StubDeclaration stub : stubDeclarations) { Node n = stub.node; Node parent = n.getParent(); String qName = n.getQualifiedName(); String propName = n.getLastChild().getString(); String ownerName = stub.ownerName; boolean isExtern = stub.isExtern; if (scope.isDeclared(qName, false)) { continue; } // If we see a stub property, make sure to register this property // in the type registry. ObjectType ownerType = getObjectSlot(ownerName); ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE); defineSlot(n, parent, unknownType, true); if (ownerType != null && (isExtern || ownerType.isFunctionPrototypeType())) { // If this is a stub for a prototype, just declare it // as an unknown type. These are seen often in externs. ownerType.defineInferredProperty( propName, unknownType, isExtern); } else { typeRegistry.registerPropertyOnType( propName, ownerType == null ? unknownType : ownerType); } } } /** * Collects all declared properties in a function, and * resolves them relative to the global scope. */ private final class CollectProperties extends AbstractShallowStatementCallback { private final ObjectType thisType; CollectProperties(ObjectType thisType) { this.thisType = thisType; } public void visit(NodeTraversal t, Node n, Node parent) { if (n.getType() == Token.EXPR_RESULT) { Node child = n.getFirstChild(); switch (child.getType()) { case Token.ASSIGN: maybeCollectMember(t, child.getFirstChild(), child, child.getLastChild()); break; case Token.GETPROP: maybeCollectMember(t, child, child, null); break; } } } private void maybeCollectMember(NodeTraversal t, Node member, Node nodeWithJsDocInfo, @Nullable Node value) { JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo(); // Do nothing if there is no JSDoc type info, or // if the node is not a member expression, or // if the member expression is not of the form: this.someProperty. if (info == null || member.getType() != Token.GETPROP || member.getFirstChild().getType() != Token.THIS) { return; } member.getFirstChild().setJSType(thisType); JSType jsType

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> = getDeclaredGetPropType(t, info, member, value); Node name = member.getLastChild(); if (jsType != null && (name.getType() == Token.NAME || name.getType() == Token.STRING)) { thisType.defineDeclaredProperty( name.getString(), jsType, false /* functions with implementations are not in externs */); } } } // end CollectProperties } /** * A stub declaration without any type information. */ private static final class StubDeclaration { private final Node node; private final boolean isExtern; private final String ownerName; private StubDeclaration(Node node, boolean isExtern, String ownerName) { this.node = node; this.isExtern = isExtern; this.ownerName = ownerName; } } /** * A shallow traversal of the global scope to build up all classes, * functions, and methods. */ private final class GlobalScopeBuilder extends AbstractScopeBuilder { private GlobalScopeBuilder(Scope scope) { super(scope); } /** * Visit a node in the global scope, and add anything it declares to the * global symbol table. * * @param t The current traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { super.visit(t, n, parent); switch (n.getType()) { case Token.ASSIGN: // Handle typedefs. checkForOldStyleTypedef(t, n); break; case Token.VAR: // Handle typedefs. if (n.hasOneChild()) { checkForOldStyleTypedef(t, n); checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo()); } break; } } @Override void maybeDeclareQualifiedName( NodeTraversal t, JSDocInfo info, Node n, Node parent, Node rhsValue) { checkForTypedef(t, n, info); super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue); } /** * Handle typedefs. * @param t The current traversal. * @param candidate A qualified name node. * @param info JSDoc comments. */ private void checkForTypedef( NodeTraversal t, Node candidate, JSDocInfo info) { if (info == null || !info.hasTypedefType()) { return; } String typedef = candidate.getQualifiedName(); if (typedef == null) { return; } // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recusive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE)); JSType realType = info.getTypedefType().evaluate(scope, typeRegistry); if (realType

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); if (candidate.getType() == Token.GETPROP) { defineSlot(candidate, candidate.getParent(), getNativeType(NO_TYPE), false); } } /** * Handle typedefs. * @param t The current traversal. * @param candidate An ASSIGN or VAR node. */ // TODO(nicksantos): Kill this. private void checkForOldStyleTypedef(NodeTraversal t, Node candidate) { // old-style typedefs String typedef = codingConvention.identifyTypeDefAssign(candidate); if (typedef != null) { // TODO(nicksantos|user): This is a terrible, terrible hack // to bail out on recusive typedefs. We'll eventually need // to handle these properly. typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE)); JSDocInfo info = candidate.getJSDocInfo(); JSType realType = null; if (info != null && info.getType() != null) { realType = info.getType().evaluate(scope, typeRegistry); } if (realType == null) { compiler.report( JSError.make( t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef)); } typeRegistry.overwriteDeclaredType(typedef, realType); // Duplicate typedefs get handled when we try to register // this typedef in the scope. } } } // end GlobalScopeBuilder /** * A shallow traversal of a local scope to find all arguments and * local variables. */ private final class LocalScopeBuilder extends AbstractScopeBuilder { /** * @param scope The scope that we're builidng. */ private LocalScopeBuilder(Scope scope) { super(scope); } /** * Traverse the scope root and build it. */ void build() { NodeTraversal.traverse(compiler, scope.getRootNode(), this); } /** * Visit a node in a local scope, and add any local variables or catch * parameters into the local symbol table. * * @param t The node traversal. * @param n The node being visited. * @param parent The parent of n */ @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n == scope.getRootNode()) return; if (n.getType() == Token.LP && parent == scope.getRootNode()) { handleFunctionInputs(parent); return; } super.visit(t, n, parent); } /** Handle bleeding functions and function parameters. */ private void handleFunctionInputs(Node fnNode) { // Handle bleeding functions. Node fnNameNode = fnNode.getFirstChild(); String fnName = fnNameNode.getString(); if (!fnName.

Closure, 87

<FILEB>
<CHANGES>
if (maybeExpr.getType() == Token.EXPR_RESULT) {
<CHANGEE>
<CHANGES>
if (maybeExpr.getFirstChild().getType() == Token.CALL) {
Node calledFn = maybeExpr.getFirstChild().getFirstChild();
<CHANGEE>
<CHANGES>
if (calledFn.getType() == Token.GETELEM) {
return false;
} else if (calledFn.getType() == Token.GETPROP &&
calledFn.getLastChild().getString().startsWith("on")) {
return false;
}
}
return true;
}
return false;
<CHANGEE>
<FILEE>
<FILEB> if (lastTrue == null || lastFalse == null || !areNodesEqualForInlining(lastTrue, lastFalse)) { break; } lastTrue.detachFromParent(); lastFalse.detachFromParent(); parent.addChildAfter(lastTrue, n); reportCodeChange(); } } /** * @return Whether the node is a block with a single statement that is * an expression. */ private boolean isFoldableExpressBlock(Node n) { if (n.getType() == Token.BLOCK) { if (n.hasOneChild()) { Node maybeExpr = n.getFirstChild(); <CHANGES> <CHANGEE> // IE has a bug where event handlers behave differently when // their return value is used vs. when their return value is in // an EXPR_RESULT. It's pretty freaking weird. See: // http://code.google.com/p/closure-compiler/issues/detail?id=291 // We try to detect this case, and not fold EXPR_RESULTs // into other expressions. <CHANGES> <CHANGEE> // We only have to worry about methods with an implicit 'this' // param, or this doesn't happen. <CHANGES> return NodeUtil.isExpressionNode(maybeExpr); <CHANGEE> } } return false; } /** * @return The expression node. */ private Node getBlockExpression(Node n) { Preconditions.checkState(isFoldableExpressBlock(n)); return n.getFirstChild(); } /** <FILEE> <SCANS> an interface. */ static FunctionType forInterface( JSTypeRegistry registry, String name, Node source) { return new FunctionType(registry, name, source); } @Override public boolean isInstanceType() { // The universal constructor is its own instance, bizarrely. return isEquivalentTo(registry.getNativeType(U2U_CONSTRUCTOR_TYPE)); } @Override public boolean isConstructor() { return kind == Kind.CONSTRUCTOR; } @Override public boolean isInterface() { return kind == Kind.INTERFACE; } @Override public boolean isOrdinaryFunction() { return kind == Kind.ORDINARY; } @Override public boolean isFunctionType() { return true; } @Override public boolean canBeCalled() { return true; } public Iterable<Node> getParameters() { Node n = getParametersNode(); if (n != null) { return n.children(); } else { return Collections.emptySet(); } } /** Gets an LP node that contains all params. May be null. */ public Node getParametersNode() { return call.parameters; } /** Gets the minimum number of arguments that this function requires. */ public int getMinArguments() { // NOTE(nicksantos): There are some native functions that have optional // parameters before required parameters. This algorithm finds the position // of the last required parameter. int i = 0; int min = 0; for (Node n : getParameters()) { i++; if (!n.isOptionalArg() && !n.isVarArgs()) { min = i; } } return min; } /** * Gets the maximum number of arguments that this function requires, * or Integer.MAX_VALUE if this is a variable argument function. */ public int getMaxArguments() { Node params = getParametersNode(); if (params != null) { Node lastParam = params.getLastChild(); if (lastParam == null || !lastParam.isVarArgs()) { return params.getChildCount(); } } return Integer.MAX_VALUE; } public JSType getReturnType() { return call.returnType; } public boolean isReturnTypeInferred() { return call.returnTypeInferred; } /** Gets the internal arrow type. For use by subclasses only. */ ArrowType getInternalArrowType() { return call; } /** * Gets the {@code prototype} property of this function type. This is * equivalent to {@code (ObjectType) getPropertyType("prototype")}. */ public FunctionPrototypeType getPrototype() { // lazy initialization of the prototype field if (prototype == null) { setPrototype(new FunctionPrototypeType(registry, this, null)); } return prototype; } /** * Sets the prototype, creating the prototype object from the given * base type. * @param baseType The base type. */ public void setPrototypeBasedOn(ObjectType base